Skip to content

📖 [Docs]: Markdown object model documents the section shape, its depth limits, and the full node schema - #37

Draft
Marius Storhaug (MariusStorhaug) wants to merge 10 commits into
mainfrom
docs-markdown-object-model-section-shape
Draft

📖 [Docs]: Markdown object model documents the section shape, its depth limits, and the full node schema#37
Marius Storhaug (MariusStorhaug) wants to merge 10 commits into
mainfrom
docs-markdown-object-model-section-shape

Conversation

@MariusStorhaug

@MariusStorhaug Marius Storhaug (MariusStorhaug) commented Aug 15, 2026

Copy link
Copy Markdown
Member

The Markdown object model documentation now describes the section shape the model actually has, states the depth limits it works within, and carries the complete node schema — every class, every property, and the CommonMark section each one derives from — in one place instead of in an issue body.

Changed: A section carries its heading instead of containing one

A section exposes the heading that opens it directly, as a level, a title, and a heading style, so $section.Level and $section.Title are one access rather than two. There is no separate heading node, which means Descendants('Section') is how headings are found and there is no traversal rule to remember about where a heading is yielded.

A title holds inline nodes rather than a string, so # Release **1.2** notes keeps its emphasis instead of flattening to text that cannot be restored. GetTitleText() reads a title as plain text when that is all that is wanted.

New: The depth limits are written down

A chain of nested sections stops at six, because sections nest only where heading levels rise and an ATX heading carries one to six # characters. Total depth of a document is unlimited, because sectioning starts over inside every block quote and list item — a block quote inside a level 6 section may open its own level 1 section. Both facts are stated as requirements rather than left to be rediscovered.

New: All four rules for where content lands

The rules that decide which section a block belongs to are stated in full: which sections a heading closes, where the new section attaches, where a block that is not a heading attaches, and what happens at the end of a container. The fourth was previously only implied by the algorithm ending.

New: The complete node schema, next to the specification

Every node type — the document, the blocks, the inlines, the enums, and the constructs that are deliberately not nodes — is documented in the design, with a worked example showing what a small document parses into. It was previously available only in the body of the epic issue that tracks the work, so it would have become unreachable as a source of truth the moment that issue closed.

New: Comments are a node type

A comment is its own kind of node at block level and at inline level, so finding a generated-region marker or a linter directive no longer means scanning raw HTML for text that starts with <!--. A comment exposes its inner text without delimiters and reports whether it was terminated, and it re-renders exactly as written. A block that carries a comment followed by other content on the same line stays an HTML block, because the specification makes the whole line one block. An unterminated comment ends with the block quote or list item holding it, and reaches the end of the document only when nothing encloses it.


Technical details

Documentation only. No classes, parser, renderer, or tests — those remain to be delivered against these documents.

Decisions taken that the source issues left open or did not cover:

  • Traversal reaches a section's Title. Removing the heading node removes the rule that yielded it before the section's children, which would have left the title inlines unreachable from Descendants() — breaking FR8 and silently dropping every link written inside a heading from $doc.Descendants('Link'). Descendants() therefore yields a section's title inlines before its children: the same single node-aware step, moved from Heading to Title.
  • An unterminated comment is bounded by its container. §4.6 ends an HTML block at "the last line of the document, or the last line of the container block containing the current HTML block", whichever comes first. Stating only the document ending would have been wrong for a comment inside a block quote or a list item, where content after the container would have been swallowed. FR17 states both, with an acceptance scenario for the container case.
  • Comment class names. MarkdownCommentBlock : MarkdownBlock and MarkdownComment : MarkdownInline, mirroring the existing MarkdownHtmlBlock and MarkdownRawHtml pair. Model comments as their own node type rather than raw HTML #36 settles the count but names neither.
  • The CommonMark example number is corrected. The comment-plus-trailing-content case is example 177, not 172; 172 is the <style type="text/css"> case. Verified against spec.txt for 0.31.2, which also confirms the ATX opening sequence of 1–6 # characters, the HTML block end condition, and <!--> and <!---> as comments. Model comments as their own node type rather than raw HTML #36 carries the same incorrect number in its body.
  • Two CommonMark section numbers were wrong in the schema as it stood in Add a specification-compliant markdown object hierarchy with ConvertFrom-Markdown and ConvertTo-Markdown #8 and are corrected on the way in: textual content is §6.9, not §6.6, and entity and numeric character references are §2.5, not §6.2.

Reported and not changed: spec.md predates the current Spec-Driven Development convention of per-requirement #### Behavioral scenarios and AC-labelled cross-cutting criteria, and still uses one consolidated gherkin block. Restructuring it is a separate change.

Implementation plan progress: completes the two specification tasks in #36 and the first two documentation tasks arising from #32. The model, parser, renderer, and test tasks in both remain open.

Standards and framework alignment:

Changed surface Standards checked Framework docs checked Result
docs/** (Markdown) PR Format, Issue Format, Spec-Driven Development Documentation Model Aligned

Issue convergence sweep: scoped to the three linked issues. None is fully satisfied by this diff — each retains open model, parser, renderer, and test work — so no closing keyword is used.

Linters, run against .github/linters/ configs: markdownlint-cli2 0 issues, textlint with the terminology rule 0 issues, codespell 0 issues.

The Process-PSModule / Get-Settings check is red here for a reason unrelated to this diff: the pinned reusable workflow references psmodule/get-psmodulesettings, which no longer exists, so every downstream job is skipped. The last four scheduled runs on main failed the same way. Filed as #38.

Relevant issues (or links)

Related work

A section now carries its heading level, title as inline nodes, and heading
style, instead of containing a separate heading node. FR2, FR4, FR6, FR7, FR8
and FR10 are reworded in place, and the acceptance criteria gain a scenario for
inline markup in a title surviving a parse and render cycle.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
FR14 states both bounds the model has always had but never wrote down: a chain
of nested sections stops at six, because sections nest only on strictly
increasing levels and an ATX heading carries one to six hash characters, while
total tree depth is unbounded because sectioning restarts inside every block
container.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
FR15 to FR20 state what a comment is in the model: exact-block recognition with
the trailing-content rule the CommonMark HTML block end condition implies, inner
text and a terminated indicator, unterminated comments running to the end of the
document, the degenerate forms, preservation of delimiters and inner spacing, and
comment-looking text in code excluded. Acceptance criteria gain a matching
feature.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The at a glance tree still showed a Heading child under Section.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The section carries Level, Title as inline nodes, and Style, so there is no
heading node left in the model. Updates the approach prose, the tree and
containment diagrams, the node members table, rendering, and section addressing,
and records the two rejected shapes in the alternatives table: the earlier
Section wrapping a heading node, and the string-title shape prototyped in
#18.

Traversal now yields a section's title inlines before its children, which keeps
FR8 true and keeps heading links reachable from Descendants().

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The pseudocode covered three; the fourth was only implicit in the function
returning. All four are now stated as prose beside it, and the pseudocode is
updated onto the merged section shape. Adds the note that the pass runs before
inline parsing, and the observation that the six-level nesting bound falls out of
rule 1 rather than being enforced.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The comment-plus-trailing-content case is example 177. Example 172 is the style
element case. Verified against spec.txt for 0.31.2.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The complete inventory - every class, property and CommonMark section reference -
existed only in the body of #8, so it would have become a closed
issue body once that Epic closed. It now lives in the design: type hierarchy,
worked example, shared members, document, blocks, inlines, enums, and the
constructs that are deliberately not nodes.

Adapted to evergreen present tense, with the merged section shape applied as it
moves: MarkdownHeading is gone, MarkdownSection carries Level, Title and Style,
and headings join the list of constructs that are not nodes. Two CommonMark
section numbers are corrected on the way in - textual content is 6.9 and entity
references are 2.5.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Comments occur at block and inline level, and a PowerShell class has a single
base, so one type cannot derive from both MarkdownBlock and MarkdownInline. The
model carries MarkdownCommentBlock and MarkdownComment, both reporting a Type of
Comment so one query finds every comment. Records the rejected single type
outside the block and inline split, and the rejected IsComment flag on the
existing HTML nodes.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CommonMark 4.6 ends an HTML block at the last line of the document OR the last
line of the container block holding it, whichever comes first. FR17 stated only
the first, which is wrong for a comment inside a block quote or a list item -
content after the container would have been swallowed. Adds an acceptance
scenario for the container case and updates the IsTerminated note.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Documentation changes NoRelease Excluded from release notes; no version bump

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant