domain: comrak options duplicated between Content::to_html and Biography::to_html and rebuilt per call #66

Closed
opened 2026-07-03 01:43:16 +00:00 by rosa · 2 comments
Owner

Found during a code review of the domain crate.

Location: crates/domain/src/models/post.rs:322 and crates/domain/src/models/user.rs:153
Type: Cleanup — duplication + wasted work

Problem

Content::to_html and Biography::to_html contain byte-identical ~25-line comrak Extension/Options blocks (same 12 extensions in the same order), each rebuilt on every render call with no shared static or helper.

Cost

Enabling a new extension for posts but not bios (or vice versa) makes the same Markdown render differently on the same page, and every render reconstructs the same Options value.

Suggested fix

Build the options once in a static OPTIONS: LazyLock<comrak::Options> and expose a single render_markdown(&str) -> String helper both methods delegate to.

Found during a code review of the `domain` crate. **Location:** `crates/domain/src/models/post.rs:322` and `crates/domain/src/models/user.rs:153` **Type:** Cleanup — duplication + wasted work ## Problem `Content::to_html` and `Biography::to_html` contain byte-identical ~25-line comrak Extension/Options blocks (same 12 extensions in the same order), each rebuilt on every render call with no shared static or helper. ## Cost Enabling a new extension for posts but not bios (or vice versa) makes the same Markdown render differently on the same page, and every render reconstructs the same Options value. ## Suggested fix Build the options once in a `static OPTIONS: LazyLock<comrak::Options>` and expose a single `render_markdown(&str) -> String` helper both methods delegate to.
Author
Owner

Agent Brief

Category: enhancement
Summary: Hoist the duplicated comrak Options into a single shared build-once static used by both markdown-rendering methods

Current behavior:
Content::to_html (post content) and Biography::to_html (user bio) in the
domain crate each construct an identical comrak::Options value inline —
the same 12 extensions in the same order — and rebuild it on every render
call. The two blocks are byte-identical copies with no shared definition, so
enabling an extension in one place but not the other would make the same
Markdown render differently depending on where it appears.

Desired behavior:
Both to_html methods reference one shared, crate-internal Options value
that is built at most once per process (e.g. a
static OPTIONS: LazyLock<comrak::Options>, matching the existing
LazyLock<Selector> idiom used for webmention HTML parsing in this crate).
Each method continues to call comrak::markdown_to_html directly, passing a
reference to the shared options. Do not introduce a render_markdown-style
wrapper function
— the maintainer explicitly decided against one; only the
options value is shared.

Key interfaces:

  • Content::to_html(&self) -> String and Biography::to_html(&self) -> String
    — signatures and rendered output unchanged
  • The shared options value — crate-internal (not part of the domain crate's
    public API), placed where both model modules can reach it
  • The extension set must be preserved exactly: alerts, description_lists,
    footnotes, highlight, inline_footnotes, insert, multiline_block_quotes,
    strikethrough, subscript, superscript, table, underline; all other options
    remain Default::default()

Acceptance criteria:

  • The comrak extension/options block is defined exactly once in the
    domain crate; both to_html methods use it
  • The options value is constructed at most once per process, not per call
  • No new public item is added to the domain crate's API, and no
    render_markdown wrapper function is introduced
  • Rendered HTML output for posts and biographies is unchanged
  • The ci and clippy mise tasks pass

Out of scope:

  • Adding, removing, or reordering comrak extensions
  • A shared rendering helper function of any kind
  • Changes to Content::parse / Biography::parse or other validation logic
  • Markdown rendering anywhere outside the domain crate
## Agent Brief **Category:** enhancement **Summary:** Hoist the duplicated comrak `Options` into a single shared build-once static used by both markdown-rendering methods **Current behavior:** `Content::to_html` (post content) and `Biography::to_html` (user bio) in the `domain` crate each construct an identical `comrak::Options` value inline — the same 12 extensions in the same order — and rebuild it on every render call. The two blocks are byte-identical copies with no shared definition, so enabling an extension in one place but not the other would make the same Markdown render differently depending on where it appears. **Desired behavior:** Both `to_html` methods reference one shared, crate-internal `Options` value that is built at most once per process (e.g. a `static OPTIONS: LazyLock<comrak::Options>`, matching the existing `LazyLock<Selector>` idiom used for webmention HTML parsing in this crate). Each method continues to call `comrak::markdown_to_html` directly, passing a reference to the shared options. **Do not introduce a `render_markdown`-style wrapper function** — the maintainer explicitly decided against one; only the options value is shared. **Key interfaces:** - `Content::to_html(&self) -> String` and `Biography::to_html(&self) -> String` — signatures and rendered output unchanged - The shared options value — crate-internal (not part of the domain crate's public API), placed where both model modules can reach it - The extension set must be preserved exactly: alerts, description_lists, footnotes, highlight, inline_footnotes, insert, multiline_block_quotes, strikethrough, subscript, superscript, table, underline; all other options remain `Default::default()` **Acceptance criteria:** - [ ] The comrak extension/options block is defined exactly once in the `domain` crate; both `to_html` methods use it - [ ] The options value is constructed at most once per process, not per call - [ ] No new public item is added to the domain crate's API, and no `render_markdown` wrapper function is introduced - [ ] Rendered HTML output for posts and biographies is unchanged - [ ] The `ci` and `clippy` mise tasks pass **Out of scope:** - Adding, removing, or reordering comrak extensions - A shared rendering helper function of any kind - Changes to `Content::parse` / `Biography::parse` or other validation logic - Markdown rendering anywhere outside the `domain` crate
Author
Owner

Implemented in commit 4522ac1: the 12-extension comrak options block now lives in a single pub(crate) static MARKDOWN_OPTIONS: LazyLock<comrak::Options> in the domain crate's models module, and both Content::to_html and Biography::to_html pass it to comrak::markdown_to_html directly — no wrapper function, per the agent brief.

Implemented in commit 4522ac1: the 12-extension comrak options block now lives in a single pub(crate) `static MARKDOWN_OPTIONS: LazyLock<comrak::Options>` in the domain crate's models module, and both `Content::to_html` and `Biography::to_html` pass it to `comrak::markdown_to_html` directly — no wrapper function, per the agent brief.
rosa closed this issue 2026-07-04 06:25:46 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
rosa/vernier#66
No description provided.