Skip to content

CND 0.5.0 renderers and converters: inline layer, Markdown, HTML - #9

Merged
Maxanox merged 17 commits into
format-0-5-0from
cnd-27-render-cnd-0-5-0
Oct 13, 2026
Merged

Maxanox merged 17 commits into
format-0-5-0from
cnd-27-render-cnd-0-5-0

Conversation

@Maxanox

@Maxanox Maxanox commented Oct 13, 2026

Copy link
Copy Markdown
Contributor

Ports everything that renders a CND to format 0.5.0, following proposal 0013. This is plan 2 of the series on the format-0-5-0 integration branch.

What changes

  • One inline layer (cnd.core.inline) turns rich text into Markdown, HTML or plain text:
    • it nests the marks, and splits a mark where two cross;
    • it judges each Markdown delimiter on the rendered content, and falls back to an HTML tag (or to plain text under text_only) when a delimiter would not parse.
  • Markdown rendering:
    • marks without a Markdown syntax (sup, sub, underline, highlight) become inline HTML by default, and plain text with the new text_only option;
    • GitHub-style alerts for admonitions, and task-list checkboxes;
    • code titles and figure descriptions;
    • lists that are tight or loose depending on their content;
    • tables rendered as GFM, as an HTML block, or as linearized rows under text_only;
    • layouts flattened.
  • HTML rendering: <aside> admonitions, CSS-grid layouts, checkboxes, nested ordered lists (the old renderer always wrote <ul>), and <meta> tags for the document's status, version and extra metadata.
  • Converters:
    • container content renders exactly once;
    • each block's marker line gathers the edges of every text inside it;
    • footnotes hold blocks, and bibliography entries are rich text;
    • status, version and extra go into the Markdown front matter.
    • A heading nested inside a container or a footnote keeps its section.
  • Goldens are regenerated, and a new elements golden covers every 0.5 element. Each golden diff is explained by a 0.5 decision.

tests/conftest.py now parks only the six modules that plan 3 ports: builder, CLI, declaration, display visitor, reconciliation and visitor.

Review

  • Every task had its own review, and one task needed a fix round: the Markdown marker line now sits apart from container blocks.
  • The final whole-branch review found three defects, all fixed in this branch:
    • a heading nested in a container lost its section;
    • a prefix written before a code block broke the Markdown;
    • delimiters were judged on the wrong characters.
  • The suite passes with 561 tests.
  • Follow-ups: CND-36 (hyperlinks in HTML) and CND-37 (smaller hardening items).

Part of CND-27

🤖 Generated with Claude Code

https://claude.ai/code/session_01XxXUiDn2QyzNTYnZpeVFVS

Maxanox and others added 17 commits October 13, 2026 15:29
…plain text (proposal 0013)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XxXUiDn2QyzNTYnZpeVFVS
…ription and table kind

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XxXUiDn2QyzNTYnZpeVFVS
…HtmlNodeRenderer ported to 0.5

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XxXUiDn2QyzNTYnZpeVFVS
…, block cells and layouts; text_only option

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XxXUiDn2QyzNTYnZpeVFVS
The list escape-callback test is deleted: render_list_markdown is gone and
list escaping stays pinned by the escape-flag test. The tight-list
assertion is deleted: proposal 0013 removes the field.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XxXUiDn2QyzNTYnZpeVFVS
… markers from every text, pools hold blocks, status/version/extra, text_only

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XxXUiDn2QyzNTYnZpeVFVS
…r line

Under a list, table or quote the marker line read as a lazy continuation
or an extra table row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XxXUiDn2QyzNTYnZpeVFVS
…ences, status/version/extra meta

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XxXUiDn2QyzNTYnZpeVFVS
…fixture; unpark test_converters

Every golden diff is a 0.5 decision: cnd_version, layout flattening and
CSS grid, nested lists keeping their own kind, RichText.lang, table.kind,
footnotes holding blocks, and the new stylesheet rules.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XxXUiDn2QyzNTYnZpeVFVS
…e the fence

Strip exactly one trailing newline before the closing fence; regenerate
the two Markdown goldens whose code blocks gained a blank line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XxXUiDn2QyzNTYnZpeVFVS
…section

A renderer emits only a heading's line and the body walk visits the
section under it, so a heading held by a quote, a list item, an
admonition, a table or layout cell, a figure or a footnote lost its
whole section, while its markers still printed. expand_headings lists
a heading followed by its section, recursively in pre-order; both
renderers render container children through it, and both converters
render footnote blocks through it. Looseness and tightness are counted
on the expanded list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XxXUiDn2QyzNTYnZpeVFVS
…n its own line

A definition's ": ", a task item's checkbox and a linearized row's
"Label: " were glued to the first block. Before a fence that turned the
rest of the document into code; before a quote or an ordered list the
block became paragraph text. The prefix now shares the line only with a
paragraph or an image; before any other block it ends its line and the
block starts on the next one, at the content indent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XxXUiDn2QyzNTYnZpeVFVS
A delimiter's inner neighbours were read from the plain value, but when
a nested mark opens or closes at the edge the real neighbour is that
mark's delimiter or tag. "xab" with emph and strike over "ab" rendered
x*~~ab~~*, which a parser reads with literal asterisks. The content is
now rendered first and its first and last characters decide; a mark
that cannot be delimited still falls back to its tag, or to plain text
under text_only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XxXUiDn2QyzNTYnZpeVFVS
With flanking judged on the rendered edge, a strong under text_only can
fall back to plain text that ends in a nested emph's "*". The guard
against a "*" opener right after a "*" read the syntax chosen, not what
was written, so the next emph opened there and the two runs merged.
It now reads the rendered text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XxXUiDn2QyzNTYnZpeVFVS
…y a blank line

A tight terms list or linearized row puts the next term or cell on the
next line. After a quote or a list that line is a lazy continuation, so
the next term or cell was read as part of the block. Anything other than
one paragraph or one image now makes the terms list loose, and the row
separate its cells with blank lines.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XxXUiDn2QyzNTYnZpeVFVS
@Maxanox
Maxanox merged commit dc980e0 into format-0-5-0 Oct 13, 2026
3 checks passed
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.

1 participant