@@ -61,22 +61,13 @@ defmodule DiffWeb.DiffLiveView do
6161 < div class = "ghd-container " id = "patch-list " >
6262 <%= for patch_id <- @ loaded_patches do %>
6363 < div id = { "patch-#{ patch_id } " } >
64- <%= case Map . get ( @ patch_contents , patch_id ) do %>
65- <% nil -> %>
66- < div class = "message-container " >
67- < div class = "message " > Loading patch <%= patch_id %> ...</ div >
68- </ div >
69- <% content -> %>
70- <%= raw ( content ) %>
71- <% end %>
64+ <%= raw ( Map . get ( @ patch_contents , patch_id , "" ) ) %>
7265 </ div >
7366 <% end %>
7467
7568 <%= if @ has_more_patches do %>
76- < div class = "message-container " >
77- < button phx-click = "load-more " class = "diff-button button " >
78- Load more patches
79- </ button >
69+ < div id = "loading-trigger " phx-hook = "InfiniteScroll " data-loaded = { length ( @ loaded_patches ) } >
70+ Loading more patches...
8071 </ div >
8172 <% end %>
8273 </ div >
@@ -215,18 +206,17 @@ defmodule DiffWeb.DiffLiveView do
215206 defp resolve_latest_version ( _package , from , to ) , do: { :ok , from , to }
216207
217208 def handle_event ( "load-more" , _params , socket ) do
218- batch_size = 10
209+ batch_size = 5
219210 { next_batch , remaining } = Enum . split ( socket . assigns . remaining_patches , batch_size )
220211
221212 socket =
222213 socket
223214 |> assign (
224- loaded_patches: socket . assigns . loaded_patches ++ next_batch ,
225215 remaining_patches: remaining ,
226216 has_more_patches: length ( remaining ) > 0
227217 )
228218
229- send ( self ( ) , { :load_patches , next_batch } )
219+ send ( self ( ) , { :load_patches_and_update , next_batch } )
230220
231221 { :noreply , socket }
232222 end
@@ -292,22 +282,60 @@ defmodule DiffWeb.DiffLiveView do
292282 { :noreply , assign ( socket , error: "Invalid diff" , generating: false ) }
293283 end
294284
295- def handle_info ( { :load_patches , patch_ids } , socket ) do
285+ def handle_info ( { :load_patches_and_update , patch_ids } , socket ) do
296286 % { package: package , from: from , to: to } = socket . assigns
297287
298288 loaded_contents =
299289 patch_ids
300290 |> Enum . reduce ( % { } , fn patch_id , acc ->
301291 case Diff.Storage . get_patch ( package , from , to , patch_id ) do
302292 { :ok , content } ->
303- Map . put ( acc , patch_id , content )
293+ sanitized_content = sanitize_utf8 ( content )
294+ Map . put ( acc , patch_id , sanitized_content )
304295
305296 { :error , reason } ->
306297 Logger . error ( "Failed to load patch #{ patch_id } : #{ inspect ( reason ) } " )
307298 Map . put ( acc , patch_id , "<div class='patch-error'>Failed to load patch</div>" )
308299 end
309300 end )
310301
302+ # Update BOTH loaded_patches AND patch_contents in ONE DOM update
303+ new_loaded_patches = socket . assigns . loaded_patches ++ patch_ids
304+
305+ socket =
306+ socket
307+ |> assign (
308+ loaded_patches: new_loaded_patches ,
309+ patch_contents: Map . merge ( socket . assigns . patch_contents , loaded_contents ) ,
310+ loading: false
311+ )
312+
313+ { :noreply , socket }
314+ end
315+
316+ def handle_info ( { :load_patches , patch_ids } , socket ) do
317+ % { package: package , from: from , to: to } = socket . assigns
318+
319+ Logger . info ( "📦 Actually loading patch content for: #{ inspect ( patch_ids ) } " )
320+
321+ loaded_contents =
322+ patch_ids
323+ |> Enum . reduce ( % { } , fn patch_id , acc ->
324+ case Diff.Storage . get_patch ( package , from , to , patch_id ) do
325+ { :ok , content } ->
326+ # Ensure content is valid UTF-8 to prevent JSON encoding errors
327+ sanitized_content = sanitize_utf8 ( content )
328+ Logger . info ( "✅ Successfully loaded patch #{ patch_id } (#{ byte_size ( content ) } bytes)" )
329+ Map . put ( acc , patch_id , sanitized_content )
330+
331+ { :error , reason } ->
332+ Logger . error ( "❌ Failed to load patch #{ patch_id } : #{ inspect ( reason ) } " )
333+ Map . put ( acc , patch_id , "<div class='patch-error'>Failed to load patch</div>" )
334+ end
335+ end )
336+
337+ Logger . info ( "🎯 Loaded #{ map_size ( loaded_contents ) } patches, updating socket assigns" )
338+
311339 socket =
312340 socket
313341 |> assign (
@@ -336,6 +364,7 @@ defmodule DiffWeb.DiffLiveView do
336364 if patch . chunks != [ ] do
337365 patch_html =
338366 Phoenix.View . render_to_string ( DiffWeb.RenderView , "patch.html" , patch: patch )
367+ |> sanitize_utf8 ( )
339368
340369 patch_id = "patch-#{ index } "
341370
@@ -360,6 +389,7 @@ defmodule DiffWeb.DiffLiveView do
360389 { :too_large , file_path } ->
361390 too_large_html =
362391 Phoenix.View . render_to_string ( DiffWeb.RenderView , "too_large.html" , file: file_path )
392+ |> sanitize_utf8 ( )
363393
364394 patch_id = "patch-#{ index } "
365395
@@ -440,6 +470,32 @@ defmodule DiffWeb.DiffLiveView do
440470 defp parse_version ( "" ) , do: { :ok , :latest }
441471 defp parse_version ( input ) , do: Version . parse ( input )
442472
473+ defp sanitize_utf8 ( content ) when is_binary ( content ) do
474+ # Replace invalid UTF-8 bytes with replacement character
475+ case String . valid? ( content ) do
476+ true ->
477+ content
478+
479+ false ->
480+ # Convert to UTF-8, replacing invalid bytes
481+ content
482+ |> :unicode . characters_to_binary ( :latin1 , :utf8 )
483+ |> case do
484+ result when is_binary ( result ) ->
485+ result
486+
487+ _ ->
488+ # If conversion fails, scrub invalid bytes
489+ content
490+ |> String . codepoints ( )
491+ |> Enum . filter ( & String . valid? / 1 )
492+ |> Enum . join ( )
493+ end
494+ end
495+ end
496+
497+ defp sanitize_utf8 ( content ) , do: content
498+
443499 defp parse_diff ( diff ) do
444500 case String . split ( diff , ":" , trim: true ) do
445501 [ app , from , to ] -> { app , from , to , build_url ( app , from , to ) }
0 commit comments