From dd2915bd763c032f6f708549923034ad677195ba Mon Sep 17 00:00:00 2001
From: Thomas Walker Lynch
Code in this manual follows the RT code format conventions. Two of those conventions appear constantly in the engine source and affect reading comprehension: vertical comma lists place the comma at the head of the line it belongs to, and the typographic middle dot serves as an ad hoc namespace separator. Namespaces are written in PascalCase, so the counter element publishes , not . Author markup, and write queries against it, using the RT-conforming spelling only. The HTML parser lowercases tag names internally as a matter of course, and CSS type selectors are matched case-insensitively in HTML documents, so a query written in the canonical spelling matches regardless of what the parser did to the source markup underneath it. This is harmless precisely because there is no namespace of unrelated elements for the lowercase form to collide with. Querying for both spellings, as some element files presently do, is therefore redundant rather than protective.
+ Two words are avoided throughout: and . Both carry several meanings at once and leave the reader to guess which was intended. May collapses permission, possibility, and probability into one syllable; RFC 2119 spent a clause on the problem and still concluded the word was best avoided. Write what is meant instead: might for possibility, can for capability, has permission to or is permitted to for permission, and a plain assertion where the hedge was doing nothing. Just is the same failure in a smaller compass, meaning variously merely, only, exactly, or recently. A word that needs its context to be understood is not carrying its own weight, and a specification is the last place to rely on the reader reconstructing intent.
+
Headings capitalize the first word only. This is not house preference but a requirement of the notation: identifiers in this system carry meaning in their case, so , , and must read the same in a heading as in code. Title casing every word makes an ordinary word indistinguishable from an identifier, and worse, makes a genuine identifier look like ordinary title casing. Sentence case keeps the distinction legible. The same rule applies to headings the engine generates for itself, such as a table of contents title.
- The invariant that makes this sound: a namespace is created only by its owning element file, in the file body. No other code may create an element's namespace, and the owning file must create it before doing anything else. Were some cooperating element to create the namespace first, presence would become true before the file body had run, and the guard would skip a file that had not in fact loaded. The invariant is what allows bare presence to serve as the flag, with no separate member. + The invariant that makes this sound: a namespace is created only by its owning element file, in the file body. No other code creates an element's namespace, and the owning file must create it before doing anything else. Were some cooperating element to create the namespace first, presence would become true before the file body had run, and the guard would skip a file that had not in fact loaded. The invariant is what allows bare presence to serve as the flag, with no separate member.
- The invariant also settles how elements communicate. They do not reach into each other's namespaces during load, because during load the other element may not have plugged in yet, and asking would violate the invariant by tempting the asker to create what it did not find. Inter-element communication happens in a later phase, through functions, when every element that is going to load has loaded. By the time the first phase runs, is complete and stable, and an element may consult any key it likes, tolerating absence as the normal condition it is. + The invariant also settles how elements communicate. They do not reach into each other's namespaces during load, because during load the other element might not have plugged in yet, and asking would violate the invariant by tempting the asker to create what it did not find. Inter-element communication happens in a later phase, through functions, when every element that is going to load has loaded. By the time the first phase runs, is complete and stable, and an element can consult any key it likes, tolerating absence as the normal condition it is.
@@ -152,7 +156,7 @@
- Task lists are lists, not sets. An earlier design used a set, which deduplicated by function identity. That protection is unnecessary, because the module guard and the namespace guard between them already prevent a file from registering twice, and it forbids something legitimate: a task may reasonably be queued more than once when it is genuinely wanted more than once. If a particular task must not be queued twice, that task's own registration can scan the list first; the lists are short and this is the rare case rather than the common one. + Task lists are lists, not sets. An earlier design used a set, which deduplicated by function identity. That protection is unnecessary, because the module guard and the namespace guard between them already prevent a file from registering twice, and it forbids something legitimate: a task might reasonably be queued more than once when it is genuinely wanted more than once. If a particular task must not be queued twice, that task's own registration can scan the list first; the lists are short and this is the rare case rather than the common one.
@@ -305,7 +309,7 @@ Establishes and , then requests the utilities, the stage manager, the theme machinery, the theme manifest, the counter layout, and the note layout.- The configuration block is ordered by dependency only at its head. The theme must be selected before the layout driver runs, because compiled configuration reads theme values. Pagination and the layout driver are intended to be independent of one another. Widgets such as the theme selector are not part of the document and may be omitted entirely, though the default theme must still be declared, since the layout reads it whether or not a reader is offered a choice. + The configuration block is ordered by dependency only at its head. The theme must be selected before the layout driver runs, because compiled configuration reads theme values. Pagination and the layout driver are intended to be independent of one another. Widgets such as the theme selector are not part of the document and can be omitted entirely, though the default theme must still be declared, since the layout reads it whether or not a reader is offered a choice.
@@ -348,7 +352,7 @@
- The ordering is forced by real dependencies rather than by preference. Generators must expand before pagination, because expansion changes height. Pages must exist before counters run, because a page number is itself a counter and there is nothing to step until the page elements exist. Cross references resolve after counters, because a reference target may contain a counter value. And the final pagination pass runs last, for the reason given under elastic pages below.
+ The ordering is forced by real dependencies rather than by preference. Generators must expand before pagination, because expansion changes height. Pages must exist before counters run, because a page number is itself a counter and there is nothing to step until the page elements exist. Cross references resolve after counters, because a reference target might contain a counter value. And the final pagination pass runs last, for the reason given under elastic pages below.
@@ -423,7 +427,7 @@
- Whether an element may be cut across a page boundary is the most subtle question in the engine, because the answer is not a property of the element. This section sets out the decomposition that makes it tractable.
+ Whether an element is permitted to be cut across a page boundary is the most subtle question in the engine, because the answer is not a property of the element. This section sets out the decomposition that makes it tractable.
- A break is legal at a position only if every enclosing node from that position up to the page root is permeable. A single impermeable ancestor vetoes the break, however permeable everything below it may be.
+ A break is legal at a position only if every enclosing node from that position up to the page root is permeable. A single impermeable ancestor vetoes the break, however permeable everything below it is.
@@ -487,7 +491,7 @@
- A break does not always fall inside a scope. It may fall in the gap between two sibling scopes, after the first has closed and before the second has opened. In that case neither sibling is cut, and neither should be soft closed. The element that spans the boundary is the parent.
+ A break does not always fall inside a scope. It might fall in the gap between two sibling scopes, after the first has closed and before the second has opened. In that case neither sibling is cut, and neither should be soft closed. The element that spans the boundary is the parent.
This follows from the chain view rather than being a separate rule. A position between two siblings has those siblings nowhere in its enclosing chain; the chain runs from the gap upward through the parent. Soft closing a sibling that merely happens to precede the gap suspends a scope that was never cut.
@@ -509,7 +513,7 @@
- Two scopes of the same counter may be cut at a single boundary: a section and a subsection within it, both spanning the break. The far side then carries two make tags bearing
The consequence for implementation is that a scope must record which counter it will exit, not which machine object. Holding a reference taken at enter time applies the exit to a machine that a nested restoration has since displaced; the live machine never receives that exit, and its list is left one level too deep. The next sibling then increments within the stale level rather than at its own, giving one point two where two was meant.
@@ -525,13 +529,13 @@
- When a child cannot be divided, the temptation is to report that the parent cannot be divided either. That is wrong, and its cost is out of all proportion to how reasonable it sounds. The parent may have a dozen further children, every one of them divisible, and reporting failure discards all of them at once. The caller, told only that nothing can be done, places the entire subtree as a single unit.
+ When a child cannot be divided, the temptation is to report that the parent cannot be divided either. That is wrong, and its cost is out of all proportion to how reasonable it sounds. The parent might have a dozen further children, every one of them divisible, and reporting failure discards all of them at once. The caller, told only that nothing can be done, places the entire subtree as a single unit.
The visible result is a page carrying the whole tail of a chapter, and the natural reading is that the indivisible child caused it. It did not. An element being larger than a page is not a defect; the defect is abandoning its siblings on account of it. A large table that occupies a page of its own is behaving exactly as intended, and the sections that follow it should go on paginating as though it were not there.
- The rule is therefore to make progress rather than to report failure. Take the indivisible child alone, let its page grow to fit, and return everything after it as a remainder to be divided normally. Progress must be measured in height and not in count, since a fragment may hold several children of no height at all â make tags, snapshots, whitespace â and emitting it yields a page bearing nothing but its own number.
+ The rule is therefore to make progress rather than to report failure. Take the indivisible child alone, let its page grow to fit, and return everything after it as a remainder to be divided normally. Progress must be measured in height and not in count, since a fragment might hold several children of no height at all â make tags, snapshots, whitespace â and emitting it yields a page bearing nothing but its own number.
- A
@@ -730,7 +734,7 @@
- All of this proceeds per named counter. An outer scope may belong to a counter with a different name, and that outer counter may have a step scope broken at the same boundary. Each name is soft closed and continuation opened independently, against its own machine.
+ All of this proceeds per named counter. An outer step scope might belong to a counter with a different name, and that outer counter might have a step scope broken at the same boundary. Each name is soft closed and continuation opened independently, against its own machine.
@@ -827,6 +831,77 @@
+ The engine reports through
+ Only
+ The point of tokens is that finishing a debugging session costs nothing. The messages are left in the source and the token is removed from the active set, so the instrumentation survives for whoever needs it next. Pagination in particular has been debugged more than once, and each time the narration has been worth more than the reasoning it replaced. Instrumentation is written to be kept, not to be removed once it has served.
+
+ Enabling a token must not change what the engine does. Narration reads state and prints; it decides nothing. The one deliberate exception is
+ A caution, since it costs time to rediscover: opening the developer tools is itself a change to the document. A docked panel narrows the viewport, which changes the width available for layout, which changes wrapping and therefore every measured height. A fault that appears with the console closed and vanishes with it open is more likely a width sensitivity than a Heisenbug in the instrumentation. Undock the panel into its own window and toggle the token again; if the behaviour follows the panel rather than the token, the measurement is depending on something it should not.
+
+ A token names an area, not a file. One area reporting under several tokens is a defect in the instrumentation rather than a feature of it, because a reader who enables the obvious name receives part of the account and has no way to tell that the rest exists. Pagination currently reports under
@@ -844,7 +919,7 @@
Any new task. Enforce with the debug shuffle; when a real dependency appears, add a phase.