feat(live_ui): :markdown_viewer renders markdown in format='rendered' (Tier 2 substrate gap)#145
Draft
ty13r wants to merge 1 commit into
Draft
feat(live_ui): :markdown_viewer renders markdown in format='rendered' (Tier 2 substrate gap)#145ty13r wants to merge 1 commit into
ty13r wants to merge 1 commit into
Conversation
… (Tier 2 substrate gap proposal) Earmark.as_html (escape: true) + HtmlSanitizeEx.markdown_html for the rendered format; <pre> with HTML-escape for raw. Uses :format attr — LiveUi.Widget reserves :mode in build_render_assigns/1's Map.drop list, so :mode-named attrs are silently always-default. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Enhances
LiveUi.Widgets.MarkdownViewerto actually render markdown whenformat == "rendered"(default). Previously the widget always wrapped:sourcein<pre>regardless of mode/format — meaning consumers couldn't get HTML-rendered markdown from the canonical kind.Two formats now supported:
"rendered"(default):Earmark.as_html(source, escape: true)→HtmlSanitizeEx.markdown_html/1(two-layer sanitization; safe for untrusted input)"raw": wraps source verbatim in<pre>(HTML-escaped)Why
Application-side ariston-ui shipped a
AristonUiWeb.OperatorV2.DocBodyPhoenix.Component (in ariston-ui#567) to render markdown on/v2/doc/<id>because the canonical widget didn't render. Once this lands, that workaround can be retired in favor of the canonical kind.Per ariston-ui orchestrator memory
feedback_substrate_gap_co_maintain_extends_to_draft_authoring, this is a Tier 2 substrate-gap proposal — DRAFT, Pascal-review REQUIRED before merge.Framework note discovered during implementation
LiveUi.Widget's render pipeline reserves:modeand drops it from the assigns passed to the wrapper module (seelive_ui/widget.exbuild_render_assigns/1—:modeappears in theMap.droplist). A widget declaringattr :modewould silently always receive the attribute default. The originalmarkdown_viewerhadattr :mode, :string, default: "rendered"— dead code; the user-provided value was always dropped.This PR uses
:formatinstead of:modeto dodge the collision. A note in the moduledoc documents the framework reservation for future widget authors. Thedata-live-ui-modeHTML attribute was renamed todata-live-ui-formatfor consistency.If
:modeshould be unreserved or the reservation should be policy, that's a separate framework-level discussion. Flagging for review.Two-layer sanitization rationale
Earmark'sescape: trueescapes raw HTML in markdown plain text but doesn't strip dangerous URL schemes (e.g.,javascript:in markdown link hrefs).HtmlSanitizeEx.markdown_html/1strips those + any remaining unsafe tags. Both layers required.The
{:error, html, _errors}return from Earmark is treated as recoverable (Earmark returns this for valid-but-imperfect markdown like unclosed emphasis) — the html field is sanitized + returned. Not a hard error.Deps
Added to
packages/live_ui/mix.exs:{:earmark, "~> 1.4"}{:html_sanitize_ex, "~> 1.4"}Consumers depending on
live_uiinherit these transitively. Worth a heads-up in release notes; the rendered format is what makes them necessary.Test plan
mix deps.get— pulls earmark + html_sanitize_ex + mochiwebmix compile --warnings-as-errors(EXIT 0)mix test packages/live_ui/test/live_ui/widgets/markdown_viewer_test.exs— 11 tests, 0 failuresTest coverage:
"rendered"format: h1/h2/h3 headings, bold + italic, unordered lists, script-tag stripping,javascript:href stripping, empty source, default-when-omitted"raw"format: source verbatim in<pre>, HTML-escapes embedded tagsPascal-review checklist
:formatthe right attr name for the rendering-mode toggle? Alternatives::viewer_format,:render_mode(with:modereserved-as-framework noted in moduledoc), or unreserving:modeat the framework level.earmark+html_sanitize_exacceptable transitive deps forlive_uiconsumers? Or should the rendering be deferred to an adapter / external sanitizer the consumer provides?unified_ui/elm_ui/desktop_uimarkdown_viewer widgets should probably honor the same:formatAPI. Out of scope for this PR; flagging for follow-up.data-live-ui-mode→data-live-ui-formatHTML attr rename break any external CSS / JS selectors? (Old attr never carried real semantic info — always"rendered"regardless of input — so risk of breakage is low.)After this lands
ariston-ui's
AristonUiWeb.OperatorV2.DocBodyPhoenix.Component can be retired in favor of<.markdown_viewer source={@doc.body_md} />via the canonical IUR pipeline. Cross-link comment posted on ariston-ui#567 once this PR opens.