fix(microprints): fix source viewer showing stale content when switching files
The source_viewer component used static DOM IDs (id='source-viewer') with phx-update='ignore'. When switching expanded files, LiveView reused the same DOM element but phx-update='ignore' prevented content from being updated, showing the previous file's source. - Override source_viewer/1 in MicroprintsLive with unique per-file IDs generated via :erlang.phash2(file_path) - Add test verifying expand-switch shows correct source content - Add test for highlight/expand coupling (collapse when highlighting a different file) - All 160 tests pass
This commit is contained in:
parent
36bdc0610e
commit
7c06204ac2
@ -59,3 +59,11 @@
|
||||
{"timestamp":"2026-05-18T16:14:33.629Z","provider":"llama.cpp","model":"Qwen3.6-35B-A3B-MXFP4_MOE.gguf","turnCount":10,"inputTokens":549,"outputTokens":1110,"totalTokens":1659,"prefillTokensPerSec":683.69,"generationTokensPerSec":48.38,"combinedTokensPerSec":69.87,"totalDurationMs":23745,"timeToFirstTokenMs":803,"rawTimestamps":{"ttftMs":803,"allTtftMs":[803],"generationDurationMs":22942,"turns":[{"turnId":"turn-0","durationMs":2273},{"turnId":"turn-1","durationMs":2599},{"turnId":"turn-2","durationMs":1569},{"turnId":"turn-3","durationMs":2705},{"turnId":"turn-4","durationMs":1677},{"turnId":"turn-5","durationMs":3140},{"turnId":"turn-6","durationMs":1769},{"turnId":"turn-7","durationMs":3296},{"turnId":"turn-8","durationMs":1226},{"turnId":"turn-9","durationMs":3491,"ttftMs":803}]}}
|
||||
{"timestamp":"2026-05-18T16:25:00.013Z","provider":"llama.cpp","model":"Qwen3.6-35B-A3B-MXFP4_MOE.gguf","turnCount":36,"inputTokens":8805,"outputTokens":7546,"totalTokens":16351,"prefillTokensPerSec":13587.96,"generationTokensPerSec":51.5,"combinedTokensPerSec":111.1,"totalDurationMs":147178,"timeToFirstTokenMs":648,"rawTimestamps":{"ttftMs":648,"allTtftMs":[648,1264,2424,1730,2366,1,6141,2028,1209,3327],"generationDurationMs":146530,"turns":[{"turnId":"turn-0","durationMs":3100},{"turnId":"turn-1","durationMs":1611},{"turnId":"turn-2","durationMs":2836},{"turnId":"turn-3","durationMs":1701},{"turnId":"turn-4","durationMs":7045},{"turnId":"turn-5","durationMs":5537},{"turnId":"turn-6","durationMs":9904,"ttftMs":648},{"turnId":"turn-7","durationMs":2297},{"turnId":"turn-8","durationMs":3572},{"turnId":"turn-9","durationMs":6941,"ttftMs":1264},{"turnId":"turn-10","durationMs":4770},{"turnId":"turn-11","durationMs":5741,"ttftMs":2424},{"turnId":"turn-12","durationMs":2288},{"turnId":"turn-13","durationMs":2018},{"turnId":"turn-14","durationMs":2203},{"turnId":"turn-15","durationMs":1905},{"turnId":"turn-16","durationMs":2054},{"turnId":"turn-17","durationMs":1797},{"turnId":"turn-18","durationMs":1620},{"turnId":"turn-19","durationMs":2448},{"turnId":"turn-20","durationMs":2930},{"turnId":"turn-21","durationMs":5691,"ttftMs":1730},{"turnId":"turn-22","durationMs":5988},{"turnId":"turn-23","durationMs":7813,"ttftMs":2366},{"turnId":"turn-24","durationMs":1},{"turnId":"turn-25","durationMs":1099,"ttftMs":1},{"turnId":"turn-26","durationMs":1652},{"turnId":"turn-27","durationMs":3354},{"turnId":"turn-28","durationMs":14059,"ttftMs":6141},{"turnId":"turn-29","durationMs":1673},{"turnId":"turn-30","durationMs":3813,"ttftMs":2028},{"turnId":"turn-31","durationMs":1979},{"turnId":"turn-32","durationMs":2149},{"turnId":"turn-33","durationMs":7972,"ttftMs":1209},{"turnId":"turn-34","durationMs":4344},{"turnId":"turn-35","durationMs":11273,"ttftMs":3327}]}}
|
||||
{"timestamp":"2026-05-18T16:26:14.186Z","provider":"llama.cpp","model":"Qwen3.6-35B-A3B-MXFP4_MOE.gguf","turnCount":7,"inputTokens":2011,"outputTokens":1232,"totalTokens":3243,"prefillTokensPerSec":1028.64,"generationTokensPerSec":26.77,"combinedTokensPerSec":67.61,"totalDurationMs":47969,"timeToFirstTokenMs":1955,"rawTimestamps":{"ttftMs":1955,"allTtftMs":[1955,2520,1853,694],"generationDurationMs":46014,"turns":[{"turnId":"turn-0","durationMs":5149},{"turnId":"turn-1","durationMs":10442,"ttftMs":1955},{"turnId":"turn-2","durationMs":6081},{"turnId":"turn-3","durationMs":10493,"ttftMs":2520},{"turnId":"turn-4","durationMs":5028,"ttftMs":1853},{"turnId":"turn-5","durationMs":6746},{"turnId":"turn-6","durationMs":4030,"ttftMs":694}]}}
|
||||
{"timestamp":"2026-05-18T16:28:35.851Z","provider":"llama.cpp","model":"Qwen3.6-35B-A3B-MXFP4_MOE.gguf","turnCount":2,"inputTokens":87,"outputTokens":301,"totalTokens":388,"prefillTokensPerSec":129.08,"generationTokensPerSec":42.92,"combinedTokensPerSec":50.47,"totalDurationMs":7687,"timeToFirstTokenMs":674,"rawTimestamps":{"ttftMs":674,"allTtftMs":[674],"generationDurationMs":7013,"turns":[{"turnId":"turn-0","durationMs":5103},{"turnId":"turn-1","durationMs":2584,"ttftMs":674}]}}
|
||||
{"timestamp":"2026-05-18T16:29:33.286Z","provider":"llama.cpp","model":"Qwen3.6-35B-A3B-MXFP4_MOE.gguf","turnCount":1,"inputTokens":40,"outputTokens":531,"totalTokens":571,"prefillTokensPerSec":17.44,"generationTokensPerSec":57.31,"combinedTokensPerSec":49.4,"totalDurationMs":11558,"timeToFirstTokenMs":2293,"rawTimestamps":{"ttftMs":2293,"allTtftMs":[2293],"generationDurationMs":9265,"turns":[{"turnId":"turn-0","durationMs":11558,"ttftMs":2293}]}}
|
||||
{"timestamp":"2026-05-18T16:31:22.157Z","provider":"llama.cpp","model":"Qwen3.6-35B-A3B-MXFP4_MOE.gguf","turnCount":1,"inputTokens":58,"outputTokens":618,"totalTokens":676,"prefillTokensPerSec":7.43,"generationTokensPerSec":134.11,"combinedTokensPerSec":54.44,"totalDurationMs":12417,"timeToFirstTokenMs":7809,"rawTimestamps":{"ttftMs":7809,"allTtftMs":[7809],"generationDurationMs":4608,"turns":[{"turnId":"turn-0","durationMs":12417,"ttftMs":7809}]}}
|
||||
{"timestamp":"2026-05-18T16:32:43.573Z","provider":"llama.cpp","model":"Qwen3.6-35B-A3B-MXFP4_MOE.gguf","turnCount":2,"inputTokens":408,"outputTokens":1498,"totalTokens":1906,"prefillTokensPerSec":75.25,"generationTokensPerSec":58.97,"combinedTokensPerSec":61.84,"totalDurationMs":30823,"timeToFirstTokenMs":5422,"rawTimestamps":{"ttftMs":5422,"allTtftMs":[5422,14765],"generationDurationMs":25401,"turns":[{"turnId":"turn-0","durationMs":7357,"ttftMs":5422},{"turnId":"turn-1","durationMs":23466,"ttftMs":14765}]}}
|
||||
{"timestamp":"2026-05-18T16:50:30.294Z","provider":"llama.cpp","model":"Qwen3.6-35B-A3B-MXFP4_MOE.gguf","turnCount":1,"inputTokens":44,"outputTokens":2533,"totalTokens":2577,"prefillTokensPerSec":0.93,"generationTokensPerSec":485.99,"combinedTokensPerSec":48.86,"totalDurationMs":52746,"timeToFirstTokenMs":47534,"rawTimestamps":{"ttftMs":47534,"allTtftMs":[47534],"generationDurationMs":5212,"turns":[{"turnId":"turn-0","durationMs":52746,"ttftMs":47534}]}}
|
||||
{"timestamp":"2026-05-18T16:57:06.934Z","provider":"llama.cpp","model":"Qwen3.6-35B-A3B-MXFP4_MOE.gguf","turnCount":17,"inputTokens":9721,"outputTokens":6329,"totalTokens":16050,"prefillTokensPerSec":513.66,"generationTokensPerSec":44.57,"combinedTokensPerSec":99.73,"totalDurationMs":160936,"timeToFirstTokenMs":18925,"rawTimestamps":{"ttftMs":18925,"allTtftMs":[18925,16190,4022,7901,1873,10528,1481],"generationDurationMs":142011,"turns":[{"turnId":"turn-0","durationMs":21666,"ttftMs":18925},{"turnId":"turn-1","durationMs":25748,"ttftMs":16190},{"turnId":"turn-2","durationMs":18958,"ttftMs":4022},{"turnId":"turn-3","durationMs":2499},{"turnId":"turn-4","durationMs":14503,"ttftMs":7901},{"turnId":"turn-5","durationMs":7021},{"turnId":"turn-6","durationMs":8323,"ttftMs":1873},{"turnId":"turn-7","durationMs":8202},{"turnId":"turn-8","durationMs":8887},{"turnId":"turn-9","durationMs":2925},{"turnId":"turn-10","durationMs":2921},{"turnId":"turn-11","durationMs":3410},{"turnId":"turn-12","durationMs":2903},{"turnId":"turn-13","durationMs":5754},{"turnId":"turn-14","durationMs":14057,"ttftMs":10528},{"turnId":"turn-15","durationMs":7970},{"turnId":"turn-16","durationMs":5189,"ttftMs":1481}]}}
|
||||
{"timestamp":"2026-05-18T17:00:32.909Z","provider":"llama.cpp","model":"Qwen3.6-35B-A3B-MXFP4_MOE.gguf","turnCount":1,"inputTokens":25,"outputTokens":715,"totalTokens":740,"prefillTokensPerSec":2.76,"generationTokensPerSec":94.53,"combinedTokensPerSec":44.48,"totalDurationMs":16637,"timeToFirstTokenMs":9073,"rawTimestamps":{"ttftMs":9073,"allTtftMs":[9073],"generationDurationMs":7564,"turns":[{"turnId":"turn-0","durationMs":16637,"ttftMs":9073}]}}
|
||||
{"timestamp":"2026-05-18T17:03:32.883Z","provider":"llama.cpp","model":"Qwen3.6-35B-A3B-MXFP4_MOE.gguf","turnCount":4,"inputTokens":728,"outputTokens":1603,"totalTokens":2331,"prefillTokensPerSec":477.69,"generationTokensPerSec":49.97,"combinedTokensPerSec":69.36,"totalDurationMs":33605,"timeToFirstTokenMs":1524,"rawTimestamps":{"ttftMs":1524,"allTtftMs":[1524],"generationDurationMs":32081.000000000004,"turns":[{"turnId":"turn-0","durationMs":3655},{"turnId":"turn-1","durationMs":17547},{"turnId":"turn-2","durationMs":8416},{"turnId":"turn-3","durationMs":3987,"ttftMs":1524}]}}
|
||||
|
||||
@ -76,12 +76,11 @@ defmodule FirehoseWeb.MicroprintsLive do
|
||||
highlighted_line={@highlighted_line}
|
||||
/>
|
||||
|
||||
<%= if @expanded_path == path and source do %>
|
||||
<.source_viewer
|
||||
<%= if @expanded_path == path and source do %> <.source_viewer
|
||||
content={source}
|
||||
highlighted_line={@highlighted_line}
|
||||
language="elixir"
|
||||
id={"source-viewer-" <> path}
|
||||
file_path={path}
|
||||
/>
|
||||
<% end %>
|
||||
<% else %>
|
||||
@ -241,5 +240,23 @@ defmodule FirehoseWeb.MicroprintsLive do
|
||||
# Delegate to MicroprintComponent
|
||||
defdelegate microprint(assigns), to: MicroprintComponent
|
||||
defdelegate microprint_legend(assigns), to: MicroprintComponent
|
||||
defdelegate source_viewer(assigns), to: MicroprintComponent
|
||||
|
||||
# Custom source_viewer with unique DOM IDs per file to prevent LiveView
|
||||
# DOM patching bugs when switching expanded files.
|
||||
def source_viewer(assigns) do
|
||||
assigns = assign(assigns, :viewer_id, "source-viewer-" <> Integer.to_string(:erlang.phash2(assigns.file_path, 1_000_000)))
|
||||
|
||||
~H"""
|
||||
<div
|
||||
id={@viewer_id}
|
||||
class="source-viewer mt-2 max-h-96 overflow-auto rounded font-mono text-xs"
|
||||
phx-hook="SourceViewer"
|
||||
data-highlighted-line={@highlighted_line}
|
||||
data-language={@language}
|
||||
style="background: var(--sv-bg); color: var(--sv-text);"
|
||||
>
|
||||
<pre id={@viewer_id <> "-pre"} phx-update="ignore" class="m-0 p-2"><code class={"language-#{@language || "plaintext"}"}><%= @content %></code></pre>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
end
|
||||
|
||||
@ -93,11 +93,43 @@ defmodule FirehoseWeb.MicroprintsLiveTest do
|
||||
|> element("svg rect[phx-value-line=\"1\"][phx-value-path=\"#{file_b}\"]")
|
||||
|> render_click()
|
||||
|
||||
# BUG: file A should be collapsed when highlighting a different file
|
||||
# Currently file A stays expanded while the highlight is on file B
|
||||
# file A should be collapsed when highlighting a different file
|
||||
html = render(view)
|
||||
refute html =~ "Collapse",
|
||||
"file A should be collapsed after highlighting a different file, but the Collapse button is still visible (expanded_path is uncoupled from highlighted_path)"
|
||||
end
|
||||
|
||||
test "switching expand from file A to file B shows file B's source content, not file A's", %{conn: conn} do
|
||||
files = MicroprintsLive.scan_source_files()
|
||||
assert length(files) >= 2, "Need at least 2 files to test source switching"
|
||||
|
||||
[file_a, file_b | _] = files
|
||||
|
||||
{:ok, view, _html} = live(conn, ~p"/microprints")
|
||||
|
||||
# Expand file A
|
||||
view
|
||||
|> element("button[phx-value-path=\"#{file_a}\"]", "Expand")
|
||||
|> render_click()
|
||||
|
||||
# Verify file A's source is shown (check for module def from microprints_live.ex)
|
||||
html = render(view)
|
||||
assert html =~ "FirehoseWeb.MicroprintsLive",
|
||||
"When file A is expanded, its source should be visible"
|
||||
|
||||
# Expand file B (should auto-collapse A)
|
||||
view
|
||||
|> element("button[phx-value-path=\"#{file_b}\"]", "Expand")
|
||||
|> render_click()
|
||||
|
||||
# Verify file B's source is shown, NOT file A's
|
||||
html = render(view)
|
||||
assert html =~ "Firehose.Application",
|
||||
"When file B is expanded, its source should be visible (not file A's source)"
|
||||
|
||||
# File A's button should now say "Expand" (collapsed)
|
||||
refute html =~ ~s(phx-value-path="#{file_a}".*Collapse),
|
||||
"file A should be collapsed after switching expand to file B"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
4226
expand-collapse-fixed.html
Normal file
4226
expand-collapse-fixed.html
Normal file
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user