feat(page): DocsUI::Markdown — markdown islands for prose authoring#26
Merged
Conversation
## 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
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.
Closes #9.
What & why
Prose is the most-written doc content type and the noisiest to hand-build from
p/code/plaincalls. This shipsDocsUI::Markdown— authors write a block of GitHub-Flavored Markdown and it renders styled identically toDocsUI::Prose, with fenced code routed throughDocsUI::Code(Rouge).The gem's own installation page spent 19
plain/codecalls 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:html_safeon free text (Critical Rule 7), and#{}in prose renders literally.DocsUI::Code— highlighted by Rouge exactly like a hand-written block; unknown / no fence language falls back to plaintext (never raises).Prose::CLASSESon the wrapper, so Markdown and hand-authored Prose read identically. Tables get the kit'stable table-sm table-zebraclasses.API delta (from a consuming site's view)
DocsUI::Markdown.new(source).DocsUI::Pagegainsmd(source)— a lowercase method, somd <<~MD … MDneeds no parens (unlikeProse()/Example()).Proseis untouched and fully supported. Backwards compatible.Behavior notes
html_block/html_inlineAST nodes skipped) — no<script>, no passthrough, no config to enable it in v1.h3/h4(h1→h3, h2→h4, deeper caps at h4). Document structure and the "On this page" TOC still come fromDocsUI::Section— use Markdown headings only for sub-headings inside a section.<p>wrapper, matching GFM output and the Prose[&_ul>li]:my-1styling.nil.to_s).Dogfood
docs/app/views/docs/pages/installation.rb's verbose prose blocks are converted tomd. OneProse()block is kept on purpose (inverify_section) to prove Prose stays supported alongsidemd.Test plan
spec/docs_ui/markdown_spec.rb(19 examples):code[&_code]treatment```ruby.code-highlight+ token spans)table/th/tdwith kit classes, first row = headerh3/h4, noh1/h2<p>wrapper<del><script>/ raw<div onclick>#{}in prosePage#mddelegationVerification
bundle exec rake— 94 examples, 0 failures, 88.7% coverage (above the 80% floor)bundle exec rubocop— 39 files, no offensescd docs && bun run build:css— the table + Prose wrapper classes are present in the build. No@source inline(...)needed: the docs site'stailwind.sources.cssalready scans the whole gem tree (docs-kit/**/*.rb), and the classes match those emitted byProse/PropTable(already scanned).Proseuntouched.