Thread $heading-level through HTML block templates#2851
Closed
rbeezer wants to merge 1 commit into
Closed
Conversation
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
7a87a3f to
7b70213
Compare
Collaborator
Author
|
Force-push was just a rebase onto current master |
rbeezer
added a commit
that referenced
this pull request
May 16, 2026
Collaborator
Author
|
Merged as-is, just edited commit message to add the PR number. Using the new profiling tools at #2829, I see a roughly 0.1s/3% speedup (3.83s down to 3.74s) for the sample article. |
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.
Thread
$heading-levelthrough HTML block templatesSummary
mode="hN"inpretext-html.xslhad two branches: awhenbranch using apassed-in
$heading-level, and anotherwisebranch that computed the levelfrom a
count()heuristic over ancestors. This PR threads$heading-levelthrough every path that produces an HTML
<hN>heading. Theotherwisebranch is now unreachable in normal operation; entering it is treated as a
bug, reported via
PTX:BUG(with the offending element's ancestor path), anddefaults to
h2.After this change,
mode="hN"is a simple lookup: emith{$heading-level},clamped to
h6. Thecount()arithmetic and thechunk-level-zero-adjustmentare gone.
Motivation: why
count()was unsatisfactoryThe previous formula approximated nesting depth by counting structural and
block-like ancestors and then patching the result with a sequence of
context-specific adjustments:
The shortcomings:
Source structure, not rendered structure. The formula counts source
ancestors, but several block types render as siblings rather than nested
children (notably
PROOF-LIKEis emitted as a sibling ofTHEOREM-LIKE).The formula compensated with a hard-coded
INNER-PROOFsubtraction. Anyfuture rendering refactor of this kind would require another patch.
Hand-maintained per-type adjustments. Each new block type needs to be
added to one or more of the
count(ancestor::*[...])lists; otherwisenesting silently miscounts. This is invisible failure — output looks
plausible but the headings drift.
Wrong inside
<page>wrappers.worksheet/pageandhandout/pageare non-structural wrappers that introduce a rendered nesting level the
formula does not see. Blocks inside
<page>were emitted one level toodeep (e.g. h5 where h4 was correct).
Wrong inside the solutions backmatter division. The
mode="solutions"recursion through exercises/projects/tasks produced proofs at one level
too deep.
Wrong for appendage headings. The
- count(self::answer|hint|solution)subtraction forced hint/answer/solution headings to the same
hNas theparent example/exercise/task, even though they sit inside the parent's
<article>in the DOM. Screen readers and outline navigation see thisas the appendage being a sibling rather than a child of the parent.
Wrong for standalone and xref-knowl pages. These are freshly framed
HTML pages with their own masthead
h1. The formula returned a levelderived from the source location of the target, which is irrelevant to
the standalone context.
In every case above, the rendering code knows the correct level because the
chain of
apply-templatescalls produces the nesting. Threading theparameter through that chain makes the level both correct and obvious from
reading the templates.
Correctness changes in output
Most of the output is byte-identical except for build timestamps. The
deliberate behavioral changes are:
hNhNworksheet/pageorhandout/pagepagewrapper now properly accounted formode="solutions"chain no longer double-incrementsh2h1h2h1All other heading levels are unchanged. Verified by directory diff against
the prior master on
sample-articleandsample-bookat chunk levels 0-4;no
PTX:BUGmessages fire.Implementation outline
Added
<xsl:param name="heading-level"/>to: the block default-matchtemplate,
mode="born-visible",mode="born-hidden",mode="body"(allvariants including the
p,p[ol|ul|dl|...],ol/li|ul/li|var/li,dl/li,gispecializations), everymode="heading-birth"andmode="heading-xref-knowl"specialization, the helperheading-*templates (
heading-list-number,heading-divisional-exercise-{serial,typed},heading-full-implicit-number,heading-type,heading-no-number,heading-non-singleton-number,heading-title-paragraphs,heading-case),every
mode="wrapped-content"template,mode="exercise-components",mode="solutions-div", themode="solutions"paths forexercise|PROJECT-LIKEandtask, theinteractiveandinteractive-coretemplates and the runestonemode="tabbed-tasks"template, the
sidebyside/sbsgroup/panel-panel/sidebyside/stacktemplates in
pretext-common.xsl, theol|ul/dltemplates, and thestructural
worksheet/page|handout/page,introduction, andconclusiontemplates.
mode="body"increments$heading-level + 1before invokingwrapped-content, codifying the "children are deeper than the parentblock" rule. Sibling-rendered
PROOF-LIKEafter aTHEOREM-LIKEbodyreceives the parent theorem's
$heading-levelunchanged.Entry points that begin a fresh page set
$heading-levelexplicitly to2:manufacture-knowlfor xref-knowl content; theinteractive/standalone-pageinvocation; themode="intermediate"summary page (forpre-content
introduction|titlepage|abstract|objectivesand post-contentconclusion|outcomes).The
mode="hN"otherwise branch was simplified to:The ancestor path makes it easy to locate the source element when a
regression slips a missing parameter past review.
Validation
For both
examples/sample-articleandexamples/sample-bookat-x chunk.levelvalues 0, 1, 2, 3, 4: zeroPTX:BUGmessages frommode="hN".Files
xsl/pretext-html.xsl— bulk of the threadingxsl/pretext-common.xsl—sidebyside,sbsgroup,sidebyside/stackxsl/pretext-runestone.xsl—mode="tabbed-tasks"Claude Opus 4.7, acting as a coding assistant for Rob Beezer