Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
334f44f
add stream section
srzeszut Oct 6, 2025
3f63bb1
add debugging streams with streams
srzeszut Oct 29, 2025
f6c6c3a
fix deleting elements at initial render
srzeszut Oct 29, 2025
99d9064
fix errors
srzeszut Oct 29, 2025
b97008f
Update elixir_display.ex
srzeszut Oct 29, 2025
a9a7e5c
add config for new streams in update
srzeszut Oct 30, 2025
7749e79
better displaying of streams
srzeszut Oct 31, 2025
64634b8
update streams ui
srzeszut Nov 3, 2025
b01ed30
refactor
srzeszut Nov 3, 2025
2769930
fix css and initial stream bug
srzeszut Nov 3, 2025
4899f2e
Update stream_utils.ex
srzeszut Nov 3, 2025
f6d05f6
Add tests
srzeszut Nov 3, 2025
3fbea24
Update tests
srzeszut Nov 3, 2025
e54f95a
fix formatting
srzeszut Nov 3, 2025
c9f1592
Update components.ex
srzeszut Nov 3, 2025
0c2bcf8
add e2e test
srzeszut Nov 4, 2025
4504f77
Update components.ex
srzeszut Nov 4, 2025
5ce190a
Update components.ex
srzeszut Nov 4, 2025
8d23a8d
Update components.ex
srzeszut Nov 4, 2025
6fc61cd
Update stream.ex
srzeszut Nov 4, 2025
4745674
add support for phoenix 1.0.17
srzeszut Nov 4, 2025
f7b7448
Add suport for old live view versions
srzeszut Nov 4, 2025
e51794e
Update streams_test.exs
srzeszut Nov 6, 2025
dddfd93
Update streams_test.exs
srzeszut Nov 6, 2025
52e1cb2
Add support for old stream diff structure
srzeszut Nov 6, 2025
1212957
move streams to new directory
srzeszut Nov 13, 2025
b75bb40
refactor
srzeszut Nov 18, 2025
75578f0
refactor
srzeszut Nov 19, 2025
dcf5f7a
small fixes and add e2e case
srzeszut Nov 19, 2025
9e56342
Update streams.ex
srzeszut Nov 19, 2025
c91628f
Merge branch 'main' into debugging-streams-with-streams
kraleppa Nov 20, 2025
ceb10f0
Merge remote-tracking branch 'origin/main' into debugging-streams-wit…
srzeszut Nov 24, 2025
46c2ff5
Update components.ex
srzeszut Nov 24, 2025
b34b9b3
refactor
srzeszut Nov 24, 2025
5d0fa0b
change section padding
srzeszut Nov 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions dev/components.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ defmodule LiveDebuggerDev.Components do
{"/embedded", "Embedded"},
{"/embedded_in_controller", "EmbeddedInController"},
{"/endless_crash_reload", "EndlessCrashReload"},
{"/stream", "Stream"},
{"/async_demo", "AsyncDemo"}
]

Expand Down
24 changes: 24 additions & 0 deletions dev/live_components/stream.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
defmodule LiveDebuggerDev.LiveComponents.StreamComponent do
use DevWeb, :live_component

def update(assigns, socket) do
items = [
%{id: "item-1", name: "first"},
%{id: "item-2", name: "second"},
%{id: "item-3", name: "third"}
]

socket
|> assign(assigns)
|> stream(:component_items, items)
|> then(&{:ok, &1})
end

def render(assigns) do
~H"""
<div>
<.box title="Component with stream" color="yellow"></.box>
</div>
"""
end
end
207 changes: 207 additions & 0 deletions dev/live_views/stream.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
defmodule LiveDebuggerDev.LiveViews.Stream do
use DevWeb, :live_view

alias LiveDebuggerDev.LiveComponents

@impl true
def mount(_params, _session, socket) do
socket =
socket
|> stream_configure(:items, dom_id: &"item-#{&1.id}")
|> stream_configure(:another_items, dom_id: &"another-#{&1.id}")
|> assign(:current_id, 0)
|> assign(:another_items_id, 0)
|> stream(:items, [])
|> stream(:another_items, [])

{:ok, socket}
end

@impl true
def render(assigns) do
~H"""
<.box title="Items Stream options">
<.button id="create-item" phx-click="create_item">Create Item</.button>
<.button id="update-item" phx-click="update_item">Update Last Item</.button>
<.button id="insert-many" phx-click="insert_many">Insert Many (Reverse Order)</.button>
<.button id="insert-at-index" phx-click="insert_at_index">Insert At Index 4</.button>
<.button id="delete-item" phx-click="delete_item">Delete Last</.button>
<.button id="reset-items" phx-click="reset_items">Reset Stream</.button>
<.button id="add-new-stream" phx-click="add_new_stream">Add new stream</.button>
<.button id="delete-both-last" phx-click="delete_both_last">
Delete Last From Both Streams
</.button>
</.box>

<hr />

<.box title="Items Stream">
<ul id="item-list" phx-update="stream">
<li :for={{id, item} <- @streams.items} id={id}>
<strong>ID:</strong> <%= item.id %>, <strong>Number:</strong> <%= item.number %>
<button phx-click="delete_by_dom_id" phx-value-id={id}>X</button>
</li>
</ul>
</.box>

<hr />

<.box title="Another Items stream options">
<.button id="create-another-item" phx-click="create_another_item">Create Another Item</.button>
</.box>

<.box title="Another Items Stream">
<ul id="another-items-list" phx-update="stream">
<li :for={{id, item} <- @streams.another_items} id={id}>
<strong><%= item.id %></strong> -><%= item.number %>
</li>
</ul>
</.box>

<hr />

<.live_component id="stream_component" module={LiveComponents.StreamComponent} />
"""
end

@impl true
def handle_event("create_item", _params, socket) do
next_id = socket.assigns.current_id
item = %{id: next_id, number: Enum.random(1..1000)}

socket =
socket
|> stream_insert(:items, item, at: 0)
|> assign(:current_id, next_id + 1)

{:noreply, socket}
end

@impl true
def handle_event("update_item", _params, socket) do
id_to_update = socket.assigns.current_id - 1

if id_to_update >= 0 do
updated_item = %{id: id_to_update, number: Enum.random(1001..2000)}

socket =
socket
|> stream_insert(:items, updated_item, update_only: true)

{:noreply, socket}
else
{:noreply, socket}
end
end

@impl true
def handle_event("insert_many", _params, socket) do
start_id = socket.assigns.current_id

items =
Enum.map(0..2, fn i ->
%{id: start_id + i, number: Enum.random(1..99)}
end)

socket =
socket
|> stream(:items, Enum.reverse(items), at: -1)
|> assign(:current_id, start_id + length(items))

{:noreply, socket}
end

@impl true
def handle_event("insert_at_index", _params, socket) do
next_id = socket.assigns.current_id
item = %{id: next_id, number: 9999}

socket =
socket
|> stream_insert(:items, item, at: 4)
|> assign(:current_id, next_id + 1)

{:noreply, socket}
end

@impl true
def handle_event("delete_item", _params, socket) do
last_id = socket.assigns.current_id - 1

if last_id >= 0 do
socket =
socket
|> stream_delete(:items, %{id: last_id})
|> assign(:current_id, last_id)

{:noreply, socket}
else
{:noreply, socket}
end
end

@impl true
def handle_event("delete_by_dom_id", %{"id" => dom_id}, socket) do
socket = stream_delete_by_dom_id(socket, :items, dom_id)
{:noreply, socket}
end

@impl true
def handle_event("reset_items", _params, socket) do
socket =
socket
|> stream(:items, [], reset: true)
|> assign(:current_id, 0)

{:noreply, socket}
end

@impl true
def handle_event("create_another_item", _params, socket) do
next_id = socket.assigns.another_items_id
item = %{id: next_id, number: Enum.random(1..100)}

socket =
socket
|> stream_insert(:another_items, item, at: -1)
|> assign(:another_items_id, next_id + 1)

{:noreply, socket}
end

@impl true
def handle_event("delete_both_last", _params, socket) do
last_item_id = socket.assigns.current_id - 1
last_another_id = socket.assigns.another_items_id - 1

socket =
socket
|> then(fn s ->
if last_item_id >= 0 do
stream_delete(s, :items, %{id: last_item_id})
else
s
end
end)
|> then(fn s ->
if last_another_id >= 0 do
stream_delete(s, :another_items, %{id: last_another_id})
else
s
end
end)
|> assign(:current_id, max(last_item_id, 0))
|> assign(:another_items_id, max(last_another_id, 0))

{:noreply, socket}
end

@impl true
def handle_event("add_new_stream", _params, socket) do
socket =
socket
|> stream(:new_items, [])

{:noreply, socket}
end
end
1 change: 1 addition & 0 deletions dev/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ defmodule LiveDebuggerDev.Router do
live("/messages", LiveDebuggerDev.LiveViews.Messages)
live("/embedded", LiveDebuggerDev.LiveViews.Embedded)
live("/endless_crash_reload", LiveDebuggerDev.LiveViews.EndlessCrashReload)
live("/stream", LiveDebuggerDev.LiveViews.Stream)
live("/async_demo", LiveDebuggerDev.LiveViews.AsyncDemo)
get("/embedded_in_controller", LiveDebuggerDev.EmbeddedLiveViewController, :embedded)
end
Expand Down
27 changes: 27 additions & 0 deletions lib/live_debugger/app/debugger/streams/actions.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
defmodule LiveDebugger.App.Debugger.Streams.Actions do
@moduledoc """
Actions for `LiveDebugger.App.Debugger.Streams` context.
"""

alias LiveDebugger.App.Debugger.Streams.StreamUtils

@type stream_update_result :: %{
functions: [function()],
config: [function()],
name: atom()
}

@spec update_stream(
stream_updates :: StreamUtils.live_stream_item(),
dom_id_fun :: (any() -> String.t())
) ::
{:ok, stream_update_result()} | {:error, String.t()}
def update_stream(stream_updates, dom_id_fun) do
with name <- stream_updates.name,
funs <- StreamUtils.stream_update_functions(stream_updates),
config <-
StreamUtils.stream_config(stream_updates, dom_id: dom_id_fun) do
{:ok, %{functions: funs, config: config, name: name}}
end
end
end
46 changes: 46 additions & 0 deletions lib/live_debugger/app/debugger/streams/queries.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
defmodule LiveDebugger.App.Debugger.Streams.Queries do
@moduledoc """
Queries for `LiveDebugger.App.Debugger.Streams` context.
"""

alias LiveDebugger.API.TracesStorage

alias LiveDebugger.App.Debugger.Streams.StreamUtils

require Logger

@type streams_result :: %{
functions: [function()],
config: map(),
names: [atom()]
}

@spec fetch_streams_from_render_traces(pid :: pid(), node_id :: TreeNode.id()) ::
{:ok, streams_result()} | {:error, String.t()}
def fetch_streams_from_render_traces(pid, node_id) do
with {:ok, render_traces} <- fetch_render_traces(pid, node_id),
stream_traces <- StreamUtils.extract_stream_traces(render_traces),
names <- StreamUtils.streams_names(stream_traces),
funs <- StreamUtils.streams_functions(stream_traces, names),
config <- StreamUtils.streams_config(stream_traces, names) do
{:ok, %{functions: funs, config: config, names: names}}
else
:end_of_table ->
:end_of_table

error ->
Logger.error("Failed to fetch streams: #{inspect(error)}")
{:error, "Failed to fetch streams"}
end
end

defp fetch_render_traces(pid, node_id) do
case TracesStorage.get!(pid, functions: ["render/1"], node_id: node_id) do
:end_of_table ->
:end_of_table

{stream_updates, _trace} ->
{:ok, stream_updates}
end
end
end
Loading