From 0429c9dbabade0919ecb09078794860398a97fe7 Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Sat, 9 May 2026 09:46:45 -0500 Subject: [PATCH] fix(docs): render API reference bullets correctly + tighter reading column Two issues made docs/api hard to read: 1. The site's custom Markdown parser only recognized `- ` bullets, so the README's `* ` bullets rendered as a single run-on paragraph with literal asterisks. Extended the parser (and tests) to accept the full CommonMark bullet set: `-`, `*`, `+`. 2. The shared .markdown-content container is 88rem wide. Comfortable for /algo's wide tables but uncomfortable for monospace prose, which prefers ~80ch. Added a .api-docs modifier class on the /docs/api page that drops max-width to 60rem, allows table cells to wrap, and slightly downsizes headings. --- assets/css/app.css | 32 +++++++++++++++++ lib/microwaveprop/markdown.ex | 34 +++++++++++++++---- lib/microwaveprop_web/live/api_docs_live.ex | 2 +- test/microwaveprop/markdown_test.exs | 18 ++++++++++ .../live/api_docs_live_test.exs | 13 +++++++ 5 files changed, 92 insertions(+), 7 deletions(-) diff --git a/assets/css/app.css b/assets/css/app.css index dcdd49c3..acaa4508 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -327,6 +327,38 @@ select.select { text-decoration: underline; } +/* API docs page — narrower reading column, since the site font is + monospace and monospace prose is uncomfortable past ~80ch. */ +.markdown-content.api-docs { + max-width: 60rem; + font-size: 0.95rem; +} + +.markdown-content.api-docs code { + word-break: break-word; +} + +.markdown-content.api-docs h1 { + font-size: 1.75rem; +} + +.markdown-content.api-docs h2 { + font-size: 1.3rem; +} + +.markdown-content.api-docs h3 { + font-size: 1.1rem; +} + +.markdown-content.api-docs table { + font-size: 0.85em; +} + +.markdown-content.api-docs th, +.markdown-content.api-docs td { + white-space: normal; +} + /* Allow map interaction through non-content parts of Leaflet popups */ .leaflet-popup { pointer-events: none; } .leaflet-popup-content-wrapper, .leaflet-popup-tip, .leaflet-popup-close-button { pointer-events: auto; } diff --git a/lib/microwaveprop/markdown.ex b/lib/microwaveprop/markdown.ex index bb2d056a..c8e4436e 100644 --- a/lib/microwaveprop/markdown.ex +++ b/lib/microwaveprop/markdown.ex @@ -45,8 +45,9 @@ defmodule Microwaveprop.Markdown do parse_blocks(remaining, [{:table, table_lines} | acc]) end - # Unordered list - defp parse_blocks(["- " <> item | rest], acc) do + # Unordered list — accept any of CommonMark's bullet markers (- * +). + defp parse_blocks([line | rest], acc) when binary_part(line, 0, min(byte_size(line), 2)) in ["- ", "* ", "+ "] do + item = binary_part(line, 2, byte_size(line) - 2) {items, remaining} = take_list(rest, [item]) parse_blocks(remaining, [{:ul, items} | acc]) end @@ -86,9 +87,28 @@ defmodule Microwaveprop.Markdown do defp take_table(["|" <> _ = line | rest], acc), do: take_table(rest, [line | acc]) defp take_table(rest, acc), do: {Enum.reverse(acc), rest} - defp take_list(["- " <> item | rest], acc), do: take_list(rest, [item | acc]) - defp take_list([" " <> cont | rest], [prev | items]), do: take_list(rest, [prev <> " " <> String.trim(cont) | items]) - defp take_list(rest, acc), do: {Enum.reverse(acc), rest} + defp take_list([line | rest] = all, acc) do + cond do + bullet_line?(line) -> + item = binary_part(line, 2, byte_size(line) - 2) + take_list(rest, [item | acc]) + + String.starts_with?(line, " ") and acc != [] -> + [prev | items] = acc + take_list(rest, [prev <> " " <> String.trim(line) | items]) + + true -> + {Enum.reverse(acc), all} + end + end + + defp take_list([], acc), do: {Enum.reverse(acc), []} + + defp bullet_line?(line) when byte_size(line) >= 2 do + binary_part(line, 0, 2) in ["- ", "* ", "+ "] + end + + defp bullet_line?(_), do: false defp take_ordered_list([line | rest], acc) do if Regex.match?(~r/^\d+\.\s/, line) do @@ -105,7 +125,9 @@ defmodule Microwaveprop.Markdown do defp take_paragraph(["" | rest], acc), do: {Enum.reverse(acc), rest} defp take_paragraph(["#" <> _ | _] = rest, acc), do: {Enum.reverse(acc), rest} defp take_paragraph(["|" <> _ | _] = rest, acc), do: {Enum.reverse(acc), rest} - defp take_paragraph(["- " <> _ | _] = rest, acc), do: {Enum.reverse(acc), rest} + defp take_paragraph([line | _] = rest, acc) when binary_part(line, 0, min(byte_size(line), 2)) in ["- ", "* ", "+ "] do + {Enum.reverse(acc), rest} + end defp take_paragraph(["```" <> _ | _] = rest, acc), do: {Enum.reverse(acc), rest} defp take_paragraph(["---" | _] = rest, acc), do: {Enum.reverse(acc), rest} diff --git a/lib/microwaveprop_web/live/api_docs_live.ex b/lib/microwaveprop_web/live/api_docs_live.ex index b8f19043..f0fea24a 100644 --- a/lib/microwaveprop_web/live/api_docs_live.ex +++ b/lib/microwaveprop_web/live/api_docs_live.ex @@ -14,7 +14,7 @@ defmodule MicrowavepropWeb.ApiDocsLive do def render(assigns) do ~H""" -
+
{raw(@content)}
diff --git a/test/microwaveprop/markdown_test.exs b/test/microwaveprop/markdown_test.exs index d072a810..10c38313 100644 --- a/test/microwaveprop/markdown_test.exs +++ b/test/microwaveprop/markdown_test.exs @@ -85,6 +85,24 @@ defmodule Microwaveprop.MarkdownTest do assert result =~ "
  • first
  • " assert result =~ "
  • second
  • " end + + test "renders unordered list with * bullets" do + result = Markdown.to_html!("* one\n* two") + assert result =~ "
      " + assert result =~ "
    • one
    • " + assert result =~ "
    • two
    • " + end + + test "renders unordered list with + bullets" do + result = Markdown.to_html!("+ alpha\n+ beta") + assert result =~ "
        " + assert result =~ "
      • alpha
      • " + assert result =~ "
      • beta
      • " + end + + test "single-line bullet (binary < 2 bytes prefix safety)" do + assert Markdown.to_html!("*") =~ "

        " + end end describe "inline formatting" do diff --git a/test/microwaveprop_web/live/api_docs_live_test.exs b/test/microwaveprop_web/live/api_docs_live_test.exs index 00a28752..84c246bd 100644 --- a/test/microwaveprop_web/live/api_docs_live_test.exs +++ b/test/microwaveprop_web/live/api_docs_live_test.exs @@ -10,4 +10,17 @@ defmodule MicrowavepropWeb.ApiDocsLiveTest do assert html =~ "/api/v1" assert html =~ "POST /auth/tokens" end + + test "renders bulleted lists rather than literal asterisks", %{conn: conn} do + {:ok, _view, html} = live(conn, ~p"/docs/api") + + assert html =~ "

          " + assert html =~ "
        • " + refute html =~ ~r/

          \*\s+Base URL/ + end + + test "applies the .api-docs class so the page has a sane reading width", %{conn: conn} do + {:ok, _view, html} = live(conn, ~p"/docs/api") + assert html =~ ~s(class="markdown-content api-docs") + end end