diff --git a/lib/bds/ai/openai_compatible_runtime.ex b/lib/bds/ai/openai_compatible_runtime.ex
index 97296a0..ffabc44 100644
--- a/lib/bds/ai/openai_compatible_runtime.ex
+++ b/lib/bds/ai/openai_compatible_runtime.ex
@@ -35,9 +35,11 @@ defmodule BDS.AI.OpenAICompatibleRuntime do
end
defp generate_blocking(url, headers, payload, request) do
- payload_json = encode_payload(payload)
+ payload_json = Jason.encode!(payload)
- log_request(:blocking, url, request, payload, payload_json)
+ Logger.debug(
+ "AI OpenAI-compatible request operation=#{inspect(Map.get(request, :operation))} model=#{inspect(request.model)} url=#{url} tools=#{payload |> Map.get("tools", []) |> length()} payload_size=#{byte_size(payload_json)}"
+ )
case HttpClient.post(url, headers, payload_json) do
{:ok, %{status: 200, body: body}} ->
@@ -75,9 +77,11 @@ defmodule BDS.AI.OpenAICompatibleRuntime do
# snapshots to `on_stream` as they arrive. The assembled message goes
# through the same normalization as the blocking path.
defp generate_streaming(url, headers, payload, request, on_stream) do
- payload_json = payload |> streaming_payload() |> encode_payload()
+ payload_json = payload |> streaming_payload() |> Jason.encode!()
- log_request(:streaming, url, request, payload, payload_json)
+ Logger.debug(
+ "AI OpenAI-compatible streaming request operation=#{inspect(Map.get(request, :operation))} model=#{inspect(request.model)} url=#{url} payload_size=#{byte_size(payload_json)}"
+ )
sse = SSE.new(on_stream, emit_interval_ms: stream_emit_interval_ms())
@@ -234,20 +238,6 @@ defmodule BDS.AI.OpenAICompatibleRuntime do
|> Map.put("stream_options", %{"include_usage" => true})
end
- defp encode_payload(payload), do: Jason.encode!(payload)
-
- defp log_request(mode, url, request, payload, payload_json) do
- tools_segment =
- case mode do
- :blocking -> " tools=#{payload |> Map.get("tools", []) |> length()}"
- :streaming -> ""
- end
-
- Logger.debug(
- "AI OpenAI-compatible#{if mode == :streaming, do: " streaming", else: ""} request operation=#{inspect(Map.get(request, :operation))} model=#{inspect(request.model)} url=#{url}#{tools_segment} payload_size=#{byte_size(payload_json)}"
- )
- end
-
defp log_http_status_error(prefix, status, body) do
Logger.error("#{prefix} HTTP error status=#{status} body=#{String.slice(body, 0, 2000)}")
end
diff --git a/lib/bds/desktop/shell_live/chat_editor.ex b/lib/bds/desktop/shell_live/chat_editor.ex
index 0ae78ea..c4ee88c 100644
--- a/lib/bds/desktop/shell_live/chat_editor.ex
+++ b/lib/bds/desktop/shell_live/chat_editor.ex
@@ -545,8 +545,13 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
@spec markdown_html(binary()) :: Phoenix.HTML.Safe.t()
def markdown_html(content) when is_binary(content) do
+ # Match Earmark's defaults (GFM tables, strikethrough, autolinks); escape
+ # raw HTML to text rather than rendering it, as the old escape: true did.
html =
- case MDEx.to_html(content) do
+ case MDEx.to_html(content,
+ extension: [table: true, strikethrough: true, autolink: true],
+ render: [escape: true]
+ ) do
{:ok, rendered} -> rendered
{:error, _reason} -> ""
end
diff --git a/lib/bds/desktop/shell_live/misc_editor.ex b/lib/bds/desktop/shell_live/misc_editor.ex
index 03ea60b..5b8eaf2 100644
--- a/lib/bds/desktop/shell_live/misc_editor.ex
+++ b/lib/bds/desktop/shell_live/misc_editor.ex
@@ -278,8 +278,13 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do
@spec markdown_html(String.t()) :: Phoenix.HTML.safe()
def markdown_html(content) do
+ # Match Earmark's defaults (GFM tables, strikethrough, autolinks); escape
+ # raw HTML to text rather than rendering it, as the old escape: true did.
html =
- case MDEx.to_html(content || "") do
+ case MDEx.to_html(content || "",
+ extension: [table: true, strikethrough: true, autolink: true],
+ render: [escape: true]
+ ) do
{:ok, rendered} -> rendered
{:error, _reason} -> ""
end
diff --git a/lib/bds/desktop/shell_live/sidebar_events.ex b/lib/bds/desktop/shell_live/sidebar_events.ex
index 2b5eb0d..fe2b573 100644
--- a/lib/bds/desktop/shell_live/sidebar_events.ex
+++ b/lib/bds/desktop/shell_live/sidebar_events.ex
@@ -3,24 +3,6 @@ defmodule BDS.Desktop.ShellLive.SidebarEvents do
alias BDS.Desktop.ShellLive.SidebarState, as: ShellSidebarState
- @event_handlers %{
- "toggle_sidebar_filters" => :toggle_sidebar_filters,
- "toggle_sidebar_archive" => :toggle_sidebar_archive,
- "toggle_sidebar_tags" => :toggle_sidebar_tags,
- "toggle_sidebar_categories" => :toggle_sidebar_categories,
- "update_sidebar_search" => :update_sidebar_search,
- "clear_sidebar_search" => :clear_sidebar_search,
- "clear_sidebar_tags" => :clear_sidebar_tags,
- "clear_sidebar_categories" => :clear_sidebar_categories,
- "toggle_sidebar_tag" => :toggle_sidebar_tag,
- "toggle_sidebar_category" => :toggle_sidebar_category,
- "select_sidebar_year" => :select_sidebar_year,
- "select_sidebar_month" => :select_sidebar_month,
- "clear_sidebar_month" => :clear_sidebar_month,
- "clear_sidebar_filters" => :clear_sidebar_filters,
- "load_more_sidebar" => :load_more_sidebar
- }
-
@spec handle(Phoenix.LiveView.Socket.t(), String.t(), map(), (Phoenix.LiveView.Socket.t(),
term() ->
Phoenix.LiveView.Socket.t())) ::
@@ -29,14 +11,7 @@ defmodule BDS.Desktop.ShellLive.SidebarEvents do
{:noreply, dispatch(event, socket, params, reload)}
end
- defp dispatch(event, socket, params, reload) do
- case Map.fetch(@event_handlers, event) do
- {:ok, handler} -> handle_event(handler, socket, params, reload)
- :error -> socket
- end
- end
-
- defp handle_event(:toggle_sidebar_filters, socket, _params, reload) do
+ defp dispatch("toggle_sidebar_filters", socket, _params, reload) do
socket =
ShellSidebarState.put_filter_panel_state(socket, fn state ->
if state.visible do
@@ -55,7 +30,7 @@ defmodule BDS.Desktop.ShellLive.SidebarEvents do
reload.(socket, socket.assigns.workbench)
end
- defp handle_event(:toggle_sidebar_archive, socket, _params, reload) do
+ defp dispatch("toggle_sidebar_archive", socket, _params, reload) do
socket
|> ShellSidebarState.put_filter_panel_state(fn state ->
%{state | archive_collapsed: not state.archive_collapsed}
@@ -63,7 +38,7 @@ defmodule BDS.Desktop.ShellLive.SidebarEvents do
|> reload.(socket.assigns.workbench)
end
- defp handle_event(:toggle_sidebar_tags, socket, _params, reload) do
+ defp dispatch("toggle_sidebar_tags", socket, _params, reload) do
socket
|> ShellSidebarState.put_filter_panel_state(fn state ->
%{state | tags_collapsed: not state.tags_collapsed}
@@ -71,7 +46,7 @@ defmodule BDS.Desktop.ShellLive.SidebarEvents do
|> reload.(socket.assigns.workbench)
end
- defp handle_event(:toggle_sidebar_categories, socket, _params, reload) do
+ defp dispatch("toggle_sidebar_categories", socket, _params, reload) do
socket
|> ShellSidebarState.put_filter_panel_state(fn state ->
%{state | categories_collapsed: not state.categories_collapsed}
@@ -79,7 +54,7 @@ defmodule BDS.Desktop.ShellLive.SidebarEvents do
|> reload.(socket.assigns.workbench)
end
- defp handle_event(:update_sidebar_search, socket, %{"sidebar_filters" => params}, reload) do
+ defp dispatch("update_sidebar_search", socket, %{"sidebar_filters" => params}, reload) do
socket
|> ShellSidebarState.put_filters(fn filters ->
Map.put(
@@ -91,25 +66,25 @@ defmodule BDS.Desktop.ShellLive.SidebarEvents do
|> reload.(socket.assigns.workbench)
end
- defp handle_event(:clear_sidebar_search, socket, _params, reload) do
+ defp dispatch("clear_sidebar_search", socket, _params, reload) do
socket
|> ShellSidebarState.put_filters(fn filters -> Map.put(filters, :search, nil) end)
|> reload.(socket.assigns.workbench)
end
- defp handle_event(:clear_sidebar_tags, socket, _params, reload) do
+ defp dispatch("clear_sidebar_tags", socket, _params, reload) do
socket
|> ShellSidebarState.put_filters(fn filters -> Map.put(filters, :tags, []) end)
|> reload.(socket.assigns.workbench)
end
- defp handle_event(:clear_sidebar_categories, socket, _params, reload) do
+ defp dispatch("clear_sidebar_categories", socket, _params, reload) do
socket
|> ShellSidebarState.put_filters(fn filters -> Map.put(filters, :categories, []) end)
|> reload.(socket.assigns.workbench)
end
- defp handle_event(:toggle_sidebar_tag, socket, %{"tag" => tag}, reload) do
+ defp dispatch("toggle_sidebar_tag", socket, %{"tag" => tag}, reload) do
socket
|> ShellSidebarState.put_filters(fn filters ->
ShellSidebarState.toggle_filter_value(filters, :tags, tag)
@@ -117,7 +92,7 @@ defmodule BDS.Desktop.ShellLive.SidebarEvents do
|> reload.(socket.assigns.workbench)
end
- defp handle_event(:toggle_sidebar_category, socket, %{"category" => category}, reload) do
+ defp dispatch("toggle_sidebar_category", socket, %{"category" => category}, reload) do
socket
|> ShellSidebarState.put_filters(fn filters ->
ShellSidebarState.toggle_filter_value(filters, :categories, category)
@@ -125,7 +100,7 @@ defmodule BDS.Desktop.ShellLive.SidebarEvents do
|> reload.(socket.assigns.workbench)
end
- defp handle_event(:select_sidebar_year, socket, %{"year" => year}, reload) do
+ defp dispatch("select_sidebar_year", socket, %{"year" => year}, reload) do
parsed_year = ShellSidebarState.parse_optional_integer(year)
socket
@@ -144,7 +119,7 @@ defmodule BDS.Desktop.ShellLive.SidebarEvents do
|> reload.(socket.assigns.workbench)
end
- defp handle_event(:select_sidebar_month, socket, %{"year" => year, "month" => month}, reload) do
+ defp dispatch("select_sidebar_month", socket, %{"year" => year, "month" => month}, reload) do
socket
|> ShellSidebarState.put_filter_panel_state(fn state ->
%{
@@ -161,7 +136,7 @@ defmodule BDS.Desktop.ShellLive.SidebarEvents do
|> reload.(socket.assigns.workbench)
end
- defp handle_event(:clear_sidebar_month, socket, _params, reload) do
+ defp dispatch("clear_sidebar_month", socket, _params, reload) do
socket
|> ShellSidebarState.put_filter_panel_state(fn state ->
%{state | archive_collapsed: false}
@@ -172,7 +147,7 @@ defmodule BDS.Desktop.ShellLive.SidebarEvents do
|> reload.(socket.assigns.workbench)
end
- defp handle_event(:clear_sidebar_filters, socket, _params, reload) do
+ defp dispatch("clear_sidebar_filters", socket, _params, reload) do
socket
|> ShellSidebarState.put_filters(fn filters ->
filters
@@ -189,7 +164,7 @@ defmodule BDS.Desktop.ShellLive.SidebarEvents do
|> reload.(socket.assigns.workbench)
end
- defp handle_event(:load_more_sidebar, socket, _params, reload) do
+ defp dispatch("load_more_sidebar", socket, _params, reload) do
socket
|> ShellSidebarState.put_filters(fn filters ->
Map.update(
diff --git a/lib/bds/rendering/filters.ex b/lib/bds/rendering/filters.ex
index 10546ee..42244cb 100644
--- a/lib/bds/rendering/filters.ex
+++ b/lib/bds/rendering/filters.ex
@@ -180,10 +180,13 @@ defmodule BDS.Rendering.Filters do
end
defp render_markdown_html(markdown) do
- # Macros above inject raw HTML (gallery, video embeds), so the render path
- # must pass HTML through untouched. unsafe: true matches the prior Earmark
- # no-escape behavior.
- case MDEx.to_html(markdown, render: [unsafe: true]) do
+ # Match Earmark's defaults: GFM tables, strikethrough and bare-URL autolinks
+ # were all on. Macros above inject raw HTML (gallery, video embeds), so
+ # unsafe: true keeps that HTML untouched as Earmark's no-escape path did.
+ case MDEx.to_html(markdown,
+ extension: [table: true, strikethrough: true, autolink: true],
+ render: [unsafe: true]
+ ) do
{:ok, html} -> html
{:error, _reason} -> markdown
end
diff --git a/test/bds/desktop/shell_live_test.exs b/test/bds/desktop/shell_live_test.exs
index db365b9..eb0b4bc 100644
--- a/test/bds/desktop/shell_live_test.exs
+++ b/test/bds/desktop/shell_live_test.exs
@@ -5465,6 +5465,18 @@ defmodule BDS.Desktop.ShellLiveTest do
refute html =~ ~r/alert(1)"
+ )
+
+ assert html =~ "
old"
+ assert html =~ "