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.
This commit is contained in:
Graham McIntire 2026-05-09 09:46:45 -05:00
parent 8f32de9086
commit 0429c9dbab
No known key found for this signature in database
GPG key ID: F4ABF488E6029E59
5 changed files with 92 additions and 7 deletions

View file

@ -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; }

View file

@ -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}

View file

@ -14,7 +14,7 @@ defmodule MicrowavepropWeb.ApiDocsLive do
def render(assigns) do
~H"""
<Layouts.app flash={@flash} current_scope={@current_scope}>
<div class="markdown-content">
<div class="markdown-content api-docs">
{raw(@content)}
</div>
</Layouts.app>

View file

@ -85,6 +85,24 @@ defmodule Microwaveprop.MarkdownTest do
assert result =~ "<li>first</li>"
assert result =~ "<li>second</li>"
end
test "renders unordered list with * bullets" do
result = Markdown.to_html!("* one\n* two")
assert result =~ "<ul>"
assert result =~ "<li>one</li>"
assert result =~ "<li>two</li>"
end
test "renders unordered list with + bullets" do
result = Markdown.to_html!("+ alpha\n+ beta")
assert result =~ "<ul>"
assert result =~ "<li>alpha</li>"
assert result =~ "<li>beta</li>"
end
test "single-line bullet (binary < 2 bytes prefix safety)" do
assert Markdown.to_html!("*") =~ "<p>"
end
end
describe "inline formatting" do

View file

@ -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 =~ "<ul>"
assert html =~ "<li>"
refute html =~ ~r/<p>\*\s+<strong>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