Skip to content

feat(book): full title→heading chain in injectBookSectionDefaults#33

Open
mmcky wants to merge 1 commit into
mainfrom
feature/book-section-defaults
Open

feat(book): full title→heading chain in injectBookSectionDefaults#33
mmcky wants to merge 1 commit into
mainfrom
feature/book-section-defaults

Conversation

@mmcky
Copy link
Copy Markdown

@mmcky mmcky commented May 15, 2026

Summary

Closes #25. Wires the full title → heading_1 → heading_2 → heading_3 chain per section: tag in injectBookSectionDefaults so the chapter-prefix machinery in enumerate.ts composes without authors needing project-level or per-page frontmatter workarounds.

The problem (from #25)

In a project using just numbering.book: true and section: tags:

project:
  numbering:
    book: true
  toc:
    - file: index
    - title: Front matter
      section: frontmatter
      children: [{ file: preface }]
    - title: Chapters
      section: chapters
      children: [{ file: ch_intro }, { file: ch_fps }]

…the enumerate.ts auto-prefix machinery never fires on chapter pages. The page-title H1 (which pandoc-generated chapter files produce as # Introduction) doesn't increment a counter unless numbering.title.enabled: true, so this.enumerator (the chapter prefix) is undefined and figures / sections / theorems render flat: Figure 1 instead of Figure 1.1, Section 1 instead of 1.1, etc.

The existing workaround required setting title.enabled and heading_2.enabled at project level — but that then leaked into frontmatter/backmatter pages, numbering ## Acknowledgements on the preface as "1". Per-page frontmatter to disable it was the only way out, and isn't viable for book-dp1 where chapter files are pandoc-generated and forbidden to hand-edit.

The fix

In injectBookSectionDefaults (a ~30-line function), wire the matching chain per section:

section: tag New defaults
chapters / appendices title.enabled ??= true, heading_2.enabled ??= true, heading_3.enabled ??= true (alongside existing heading_1 wiring)
frontmatter / backmatter title.enabled ??= false, heading_2.enabled ??= false, heading_3.enabled ??= false (matching existing heading_1 disable)

Also changes the legacy hard h1.enabled = false on frontmatter/backmatter to ??= false for symmetry. The hard assignment had made it impossible for an author to override (e.g. opting a Preface page back into the chapter sequence). With ??= the layered precedence is uniform: page frontmatter > project config > section default > hardcoded fallback.

Tests added

10 new tests in bookSection.spec.ts:

  • chapters / appendices seed the full chain enabled
  • frontmatter / backmatter seed the full chain disabled
  • page override wins on chapters (e.g. author disables heading_2)
  • page override wins on frontmatter (e.g. "Chapter 0: Preface" — previously impossible)
  • project-level numbering still wins over section default

All 19 bookSection.spec.ts tests pass; full myst-cli suite 293/293 green.

Precedence preserved

This change only adds defaults, never overrides. The mystmd inheritance model (overrides cascade down the document tree) is fully honored:

  1. Page frontmatter (highest)
  2. Project myst.yml
  3. Section defaults — this layer
  4. Hardcoded mystmd defaults (lowest)

All assignments use ??= so any higher layer's setting flows through untouched.

Test plan

  • bookSection.spec.ts (19 tests) pass
  • myst-cli full suite (293 tests) pass
  • Deploy to book-dp1 and confirm chapter pages render 1.1, 1.1.1, Figure 1.1, etc. with just numbering.book: true in myst.yml
  • Confirm preface / backmatter pages stay unnumbered in the same project

🤖 Generated with Claude Code

Wires `title.enabled`, `heading_2.enabled`, and `heading_3.enabled` per
`section:` tag in `injectBookSectionDefaults` so the chapter-prefix
machinery in enumerate.ts composes without authors needing project-level
workarounds.

Background: `enumerate.ts`'s book-mode auto-prefix only fires when the
page-title heading is itself numbered (it provides `this.enumerator`
for figures/theorems/sub-headings to prepend). The page-title heading
in turn requires `numbering.title.enabled: true`. PR #22 wired
`heading_1.enabled` per section but missed `title`, so chapter pages
rendered `Figure 1` / `Section 1` instead of `Figure 1.1` / `Section 1.1`
on a default `numbering.book: true` config. Authors hit this on pandoc-
generated chapter files (LaTeX `\chapter{}` → MD `# Title`) where they
cannot add per-page frontmatter.

This patch:

- chapters / appendices: seeds `title.enabled ??= true`,
  `heading_2.enabled ??= true`, `heading_3.enabled ??= true` alongside
  the existing `heading_1` defaults.
- frontmatter / backmatter: seeds those same keys to `??= false` so a
  project-level `heading_2.enabled: true` (often used so chapters number
  correctly) doesn't leak through and number the preface's `##` headings.
- Changes the legacy hard `h1.enabled = false` on frontmatter/backmatter
  to `??= false` for symmetry — a page that explicitly wants its title
  numbered (e.g. "Chapter 0: Preface") can now override the section
  default. The hard assignment had made this impossible.

All assignments use `??=` so the layered precedence (page > project >
section default > hardcoded) is preserved. Adds 10 new tests covering
the new wiring and the per-page override paths.

Closes #25.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 15, 2026 02:31
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates injectBookSectionDefaults in myst-cli to seed book-mode numbering defaults per TOC section: tag so that chapter-prefix enumeration composes correctly when a page’s first H1 is absorbed as the page title (e.g., pandoc chapter conversions).

Changes:

  • Extend section-default injection to cover numbering.title, numbering.heading_2, and numbering.heading_3 (in addition to existing heading_1) for chapters/appendices, and seed disabled defaults for frontmatter/backmatter.
  • Add unit tests covering the new defaults and override behavior.
  • Add a changeset documenting the patch release.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
packages/myst-cli/src/process/mdast.ts Extends injectBookSectionDefaults to seed title + heading_2/3 defaults by book section.
packages/myst-cli/src/process/bookSection.spec.ts Adds tests validating the new seeding/override behavior.
.changeset/book-section-defaults.md Declares a myst-cli patch release and describes the behavior change.
Comments suppressed due to low confidence (1)

packages/myst-cli/src/process/bookSection.spec.ts:200

  • This test (and its comment) uses heading_1.start: 0 to illustrate a “Chapter 0: Preface”, but the frontmatter validator enforces start >= 1 for numbering items (myst-frontmatter validateNumberingItem, min: 1). In the real pipeline a page-level start: 0 will be rejected/ignored, so this example doesn’t reflect a supported override unless validation rules are changed.
  test('page override wins on a frontmatter page: enabling title is preserved', () => {
    // The other direction: an author has a `Preface` they want numbered
    // as part of the chapter sequence ("Chapter 0: Preface"). They write
    // `numbering.heading_1: { enabled: true, start: 0, label: "Chapter %s" }`.
    // The section default uses ??=, so the page wins and the preface
    // gets numbered despite being tagged `section: frontmatter`.
    //
    // This is the symmetry fix: the legacy `h1.enabled = false` (hard
    // assignment) made this impossible.
    const fm: PageFrontmatter = {
      numbering: {
        book: { enabled: true },
        heading_1: { enabled: true, start: 0, label: 'Chapter %s' },
        title: { enabled: true },
      },

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +153 to +162
// For frontmatter / backmatter we seed `false` so a project-level
// `numbering.heading_2.enabled: true` (often needed to make chapter
// pages work) does not leak through and number the preface's `##`
// headings as `1, 2, 3`.
//
// All assignments use `??=` so per-page and per-project overrides
// always win — this layer is *defaults*, not mandates.
numbering.title ??= {};
numbering.heading_2 ??= {};
numbering.heading_3 ??= {};
Comment on lines +149 to +158
test('section defaults seed false for frontmatter even when project enables h2', () => {
// Before #25's fix, a project enabling heading_2 (a workaround for
// chapter prefixing) would leak through to frontmatter pages and
// number the preface's `##` headings as 1, 2, 3. Now the section
// default explicitly seeds heading_2.enabled = false for frontmatter,
// so the preface stays unnumbered regardless of project config.
//
// (Project-level numbering merges happen after this function runs in
// the full pipeline, but the section-default false acts as the page-
// local override — closer to the page beats the project setting.)
"myst-cli": patch
---

Book mode: `injectBookSectionDefaults` now seeds the full `title → heading_1 → heading_2 → heading_3` chain per section tag, fixing chapter-prefix composition. With `numbering.book: true` and `section: chapters` (or `appendices`) on a TOC entry, figures, sections, and theorems on chapter pages now render as `1.1`, `1.1.1`, etc. instead of the broken flat `1`, `1`. The matching `section: frontmatter` / `backmatter` branch seeds `false` for the same chain so preface / back-matter pages stay unnumbered even when a project enables those depths for chapters. All assignments use `??=` so per-page and per-project overrides always win — including the previously-impossible case of enabling numbering on a frontmatter page. Closes QuantEcon/mystmd#25.
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.

Book mode: injectBookSectionDefaults misses numbering.title and heading_2/3 wiring

2 participants