Skip to content

feat(page): DocsUI::Markdown — markdown islands for prose authoring#26

Merged
mhenrixon merged 1 commit into
mainfrom
issue-9-docs-ui-markdown
Jul 3, 2026
Merged

feat(page): DocsUI::Markdown — markdown islands for prose authoring#26
mhenrixon merged 1 commit into
mainfrom
issue-9-docs-ui-markdown

Conversation

@mhenrixon

Copy link
Copy Markdown
Owner

Closes #9.

What & why

Prose is the most-written doc content type and the noisiest to hand-build from p/code/plain calls. This ships DocsUI::Markdown — authors write a block of GitHub-Flavored Markdown and it renders styled identically to DocsUI::Prose, with fenced code routed through DocsUI::Code (Rouge).

The gem's own installation page spent 19 plain/code calls on ~7 sentences; that's now Markdown.

How it works

Parses GFM with commonmarker (v2, comrak — precompiled; GFM tables + strikethrough + autolink by default) and walks the AST emitting Phlex nodes — it never raws commonmarker's HTML. That buys:

  • Phlex-native escaping — author text is escaped by Phlex, so no html_safe on free text (Critical Rule 7), and #{} in prose renders literally.
  • Fenced code delegated to DocsUI::Code — highlighted by Rouge exactly like a hand-written block; unknown / no fence language falls back to plaintext (never raises).
  • The exact Prose::CLASSES on the wrapper, so Markdown and hand-authored Prose read identically. Tables get the kit's table table-sm table-zebra classes.

API delta (from a consuming site's view)

  • New component DocsUI::Markdown.new(source).
  • DocsUI::Page gains md(source) — a lowercase method, so md <<~MD … MD needs no parens (unlike Prose() / Example()).
  • Nothing else changes. Prose is untouched and fully supported. Backwards compatible.

Behavior notes

  • Raw HTML is dropped (html_block / html_inline AST nodes skipped) — no <script>, no passthrough, no config to enable it in v1.
  • Markdown headings render as styled h3/h4 (h1→h3, h2→h4, deeper caps at h4). Document structure and the "On this page" TOC still come from DocsUI::Section — use Markdown headings only for sub-headings inside a section.
  • Tight vs loose lists honored — tight list items render without a <p> wrapper, matching GFM output and the Prose [&_ul>li]:my-1 styling.
  • Source normalized to UTF-8 at the boundary (commonmarker v2 raises on US-ASCII / nil.to_s).

Dogfood

docs/app/views/docs/pages/installation.rb's verbose prose blocks are converted to md. One Prose() block is kept on purpose (in verify_section) to prove Prose stays supported alongside md.

Test plan

spec/docs_ui/markdown_spec.rb (19 examples):

Asserts
paragraphs / strong / em / links with Prose wrapper classes
inline code gets the [&_code] treatment
fenced ```ruby routed through Rouge (.code-highlight + token spans)
unknown / no fence language plaintext fallback, no raise
GFM table table/th/td with kit classes, first row = header
headings demoted to h3/h4, no h1/h2
tight bullet + ordered lists no <p> wrapper
block quote + thematic break
GFM strikethrough <del>
raw <script> / raw <div onclick> dropped
#{} in prose literal (Phlex escaping)
HTML-special chars escaped
nil / empty / US-ASCII source never raises
header-only table no body, no drop error
Page#md delegation renders Prose-styled Markdown

Verification

  • bundle exec rake94 examples, 0 failures, 88.7% coverage (above the 80% floor)
  • bundle exec rubocop39 files, no offenses
  • cd docs && bun run build:css — the table + Prose wrapper classes are present in the build. No @source inline(...) needed: the docs site's tailwind.sources.css already scans the whole gem tree (docs-kit/**/*.rb), and the classes match those emitted by Prose / PropTable (already scanned).
  • Backwards compatible — no existing component changed behavior; Prose untouched.

## Summary

Prose is the most-written doc content and the noisiest to hand-build from
p/code/plain calls. Ship DocsUI::Markdown: authors write a block of GFM and it
renders styled identically to DocsUI::Prose, with fenced code routed through
DocsUI::Code (Rouge).

It parses GFM with commonmarker (v2, comrak) and walks the AST emitting Phlex
nodes — it never `raw`s commonmarker's HTML. That gives Phlex-native escaping
(no html_safe on author text — Critical Rule 7; #{} renders literally), fenced
code delegated to DocsUI::Code, GFM tables with the kit's table classes, and the
exact Prose typography classes on the wrapper.

- DocsUI::Page gains `md(source)` — a lowercase method so `md <<~MD … MD` needs
  no parens (unlike Prose()/Example()).
- Raw HTML is dropped (html_block/html_inline AST nodes skipped) — no <script>,
  no passthrough, no config to enable it.
- Markdown headings render as styled h3/h4; structure + the TOC stay with
  DocsUI::Section.
- Tight vs loose lists honored (tight items have no <p> wrapper, matching GFM
  and the Prose `[&_ul>li]` styling).
- Source is normalized to UTF-8 at the boundary (commonmarker v2 raises on
  US-ASCII / nil.to_s).

Dogfooded: installation.rb's verbose prose blocks converted to `md` (one Prose()
kept on purpose to prove Prose stays supported). README gains an "Authoring with
Markdown" section (the <<~'MD' single-quote tip; Sections still own structure).

## Test Coverage

- spec/docs_ui/markdown_spec.rb (19 examples): paragraphs/strong/em/links with
  Prose classes; inline code; fenced ruby through Rouge (token spans); unknown +
  no-language fence → plaintext; GFM table with kit classes; headings demoted to
  h3/h4; tight lists; block quote + thematic break; strikethrough; <script>
  dropped; raw HTML block dropped; #{} literal; HTML-special escaping; nil/empty
  source; non-UTF-8 source; header-only table; the Page#md delegation.

## Verification
- [x] bundle exec rake (94 examples, 0 failures; 88.7% coverage) passes
- [x] bundle exec rubocop (39 files, no offenses) passes
- [x] cd docs && bun run build:css — table + Prose wrapper classes present in
      the build (whole gem tree is already @source-scanned; no @source inline
      needed)

Closes #9
@mhenrixon mhenrixon self-assigned this Jul 3, 2026
@mhenrixon mhenrixon merged commit 1c76915 into main Jul 3, 2026
3 checks passed
@mhenrixon mhenrixon deleted the issue-9-docs-ui-markdown branch July 4, 2026 14:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(markdown): DocsUI::Markdown — markdown islands for prose authoring

1 participant