Skip to content

Read the book in one order: outline, numbers, Next up, Previous/Next and the nav follow the containment tree - #495

Merged
Deicyde merged 17 commits into
mainfrom
ui/book-order
Oct 31, 2026
Merged

Deicyde merged 17 commits into
mainfrom
ui/book-order

Conversation

@Deicyde

@Deicyde Deicyde commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Part of making the published site read well. The explorer's outline, Results, statement numbers, Next up, Previous/Next and SUMMARY.md now follow one reading order: the book's own. It is render-side only, with no client JS and no JSON key.

Problem

  • The index used path order. dependencies/index.json, which drives the explorer's outline and Results, listed nodes in sorted-path order (graph.py:512). Hartshorne's outline put Cohomology second, ahead of Projective varieties. zeta(7)'s outline put "Irrationality proof" second; the book puts it last.
  • Next up and numbers ignored the book. They followed dependency order, not reading order.
  • Previous/Next and SUMMARY.md followed every link from the landing page, depth first. A landing-page shortcut to a deep milestone pulled that milestone, and all its pages, ahead of the contents page. Hatcher's Previous/Next opened with local degree and the suspension pages before the fundamental group.

Change

  • One walk: _reading_pages goes down the containment tree from the landing page. Each page's children come in the order that page links them:

    • a child the page links directly takes its first direct link;
    • a child the page links only through its subtree (a section or a result, not the chapter's README) takes the page's first link into that subtree;
    • a direct link wins, so a prose shortcut to a deep milestone doesn't pull its chapter forward;
    • the children the page doesn't link at all follow, each subtree after the subtrees it rests on (a subtree-level topological sort; ties fall back to id order, and a cycle is broken at its earliest member).

    A link to a page elsewhere in the tree doesn't move it.

  • Every consumer uses it:

    • _book_page_order returns the landing page followed by that walk, so SUMMARY.md (Nest each book page under its chapter's README in SUMMARY.md #481's nesting) and Previous/Next follow it;
    • write_graph_pages emits the index in that order, and feeds the same order to write_search_index and extract_wiki_links, so relation positions and wiki-link offsets agree;
    • Next up and statement numbering follow it, with cards on a page in their order on that page.
  • The nav gains pages. A page its parent doesn't link now joins SUMMARY.md and Previous/Next, after its linked siblings; the link walk skipped it. The published files are the same. Hartshorne gains roadmap/intersection-theory/chow/cycles/exercises/README.md, and a book with no landing README gains all its pages. On Hatcher, zeta(7) and the bundled example, both walks list the same pages.

  • One changelog line (Read the book in one order: outline, numbers, Next up, Previous/Next and the nav follow the containment tree #495): sites get new statement numbers, and unlinked pages join the nav.

Tests

Out of scope

  • "Node" numbers skip. Containers and narrative pages take numbers from the same "Node" counter as cards whose declaration has no label, so the visible Node numbers skip (Hartshorne's first is now 32; it was 4 before). That predates this PR, which only changes the order of that loop. Numbering kinds are unchanged.

Changes since the review

jackmccarthy-77's review found nothing blocking; these address its six notes.

  • The walk rule (note 1). A page that links a scope's sections but not the scope's README now reads the scope at its first link into it, and direct links still win. On Hartshorne, étale cohomology moves to the Weil conjectures page's first link into it, right after zeta and before frobenius, so frobenius-point-count-trace-formula no longer comes before the Lefschetz fixed-point result it rests on. Page orders before and after (_book_page_order on load_graph, landing page included):
    • Hartshorne, 201 pages: only positions 191-200 move.

      # Before After
      191 frobenius etale-cohomology
      192 linear-algebra etale-cohomology/foundations
      193 deductions etale-cohomology/properties
      194 weil etale-cohomology/comparison
      195 examples etale-cohomology/cycle-classes
      196 etale-cohomology frobenius
      197 etale-cohomology/foundations linear-algebra
      198 etale-cohomology/properties deductions
      199 etale-cohomology/comparison weil
      200 etale-cohomology/cycle-classes examples

      (all under roadmap/weil-conjectures/)

    • Hatcher (41), zeta(7) (7) and the bundled example (4): identical. Hatcher still opens with the fundamental group; its roadmap's prose shortcut to degree-realization-suspension doesn't pull Homology first.

  • Unlinked siblings (note 2). They now follow a subtree-level topological sort: subtree A comes before subtree B if anything in B depends on anything in A, with ties broken by id order and a cycle at its earliest member. Each dependency orders the two sibling subtrees just below its ends' nearest common container, so the precedence is built once per walk, and only when some page has two or more unlinked children. None of the four real sites changes.
  • Page-set wording (note 3). The description and the CHANGELOG line now say that unlinked pages join SUMMARY.md and Previous/Next; the line names Read the book in one order: outline, numbers, Next up, Previous/Next and the nav follow the containment tree #495.
  • Tests (note 4). Each was run against a patched copy with its mutant, failed there, and passes on this head:
    • the landing-shortcut variant asserts SUMMARY.md's chapter order and follows the Next links from the contents page (the landing page carries no Previous/Next, since the strip starts at the contents page). Kills main's link walk pasted into _book_page_order, and the link walk used for Previous/Next alone;
    • a chapter linked only through a section reads at that link, and a shortcut into a chapter doesn't beat its direct link. Kills dropping subtree links and letting subtree links win;
    • unlinked chapters read after the chapters they rest on, including a subtree cycle. Kills sorting unlinked children by path, keeping id order, and releasing a cycle from the other end;
    • _reading_order keeps nodes on pages outside the book one page at a time. Kills dropping the per-page part of its sort key;
    • test_book_order_follows_a_1200_deep_book replaces the 1,200-page link chain: a 1,200-deep containment chain whose pages link their child page before a note that sorts first, which the walk must follow without recursion. Kills removing link reading.
  • Stale wording (note 5). _render_summary_nav's docstring and the book-nav and book-order test comments describe the tree walk.
  • Out of scope (note 6). Noted above; numbering kinds unchanged.

Changes since the independent checks

  • Ties start from id order, as described. They used to start from the depth-first topological order, which differs from id order only when the unlinked siblings themselves declare Depends on.
  • Cycles: when the subtree sort is stuck, the earliest sibling that sits on a cycle goes next, not an earlier one that only waits behind the cycle. A new test kills the old release rule.
  • Result links: a link to a result inside a chapter counts as a link into that chapter, as the rule says. A new case in the section-link test kills the old book-pages-only reading.
  • Deep books: _containment_chain tracks visited nodes in a set, so the precedence pass no longer costs depth squared per chain. A new 1,200-deep scale test runs the precedence pass and the subtree sort.
  • Hartshorne (201 pages), Hatcher (41), zeta(7) (7) and the bundled example (4) give the same page order as before these fixes.

Merged with main

Posted by PR swarm: PR Swarm Lead

The index drives the explorer outline and Results, and listed nodes in
dependency order with alphabetical seeds, so a book's last chapter could
come second. Render now passes the book's reading order (page rank, or
the nearest ancestor's, then dependency order) to write_graph_pages,
which sorts the index once and reuses it for the wiki-link layer.
Next up took the first ready result in dependency order, so it could
name a result from a late chapter while an earlier one was ready. Render
now works out the book's page order before the landing page, using the
planned chapter pages, and orders nodes by page, then by card place on
the page. Next up, Previous/Next, SUMMARY.md and the search index all
read that one order.
Each kind kept its own counter but counted in dependency order, so a
page could read Theorem 27..34 above Theorem 22. Numbers now follow the
book's reading order, so each kind's numbers rise down each page and
across chapters; references read the same numbers. Kinds still keep
separate counters, so a page can read Definition 3 before Theorem 1.
The reading order walked links from the landing page, so a news link
there to a deep milestone made it chapter one for numbering, Next up
and the search index; Hatcher's book opened in local degree, not the
fundamental group. A page its parent did not list took its ancestor's
rank and its cards interleaved with the ancestor's.

Now the order walks the containment tree from the landing page, each
page's children in the order it links them, then the unlisted ones,
and each page's cards stay together. SUMMARY.md and previous/next keep
the link walk. The fallback sections come from one helper that the
chapter and its numbering share, and the dead planned pages are gone.
_book_page_order followed every link from the landing page depth first, so a
landing-page shortcut to a deep milestone pulled that milestone, and all of
its pages, ahead of the contents page. Hatcher's Previous/Next opened with
local degree and the suspension pages before the fundamental group. It now
returns the landing page followed by _reading_pages, the containment walk
statement numbers, Next up and the search index already use. The set of pages
is unchanged: on Hatcher and the example, the two walks list the same pages,
and only the order moves.
From jackmccarthy-63's follow-up to #481. The test adds a sibling chapter,
Cohomology, that Homology links before its own pages, and orders Homology's
links so the expected nav is the same under the old link walk and the
containment walk. It fails for siblings sorted by path, for a page beside a
README placed under the folder above, and for depth counted in book order
without the tree.
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Oct 28, 2026
@Deicyde
Deicyde marked this pull request as ready for review October 28, 2026 22:34
@Deicyde

Deicyde commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

Review of #495 at 0b2aee5: nothing blocks merge. The walk follows its stated rule, and every consumer reads the same order. I rendered four real sites on main and on this head, and the PR's claims hold. Six notes follow; the first two are about the rule itself.

Notes

  1. A contents page that links a scope's sections but not the scope's README puts that scope last. _reading_pages (autoform_cli/render.py:1981-1983) counts only links to direct children. A child the page links only through its subtree counts as unlinked and goes after every linked sibling.
    • Hartshorne. weil-conjectures/README.md lists zeta, then the four etale-cohomology/*/README.md sections, then frobenius and the rest, but never etale-cohomology/README.md. So étale cohomology now reads after the results that rest on it: frobenius-point-count-trace-formula is Theorem 1134 and rests on ell-adic-lefschetz-fixed-point, Theorem 1165.
    • Context. Main's sidebar already put étale cohomology last, and Previous/Next now agrees with it. This head follows the rule as written. Hartshorne also renders on neither main nor this head today (source_units, coverage table), so changing this now renumbers nobody twice.
    • Fix. If a page never links a child directly, place the child at the page's first link into its subtree, and let direct links win. Without that precedence, Hatcher's prose shortcut to degree-realization-suspension would pull Homology first. A prototype of about 15 lines changes only those ten Hartshorne positions. It leaves Hatcher, zeta(7), the bundled example and 22 small edge-case blueprints byte-identical, and this PR's tests pass with it.
  2. "The children it doesn't link, in dependency order" is id order for scopes. children comes from status.topological_order(graph) (autoform_cli/render.py:1970-1973). A container's dependencies come only from its own README's Depends on, and none of the four real vaults declares any.
    • So unlinked sibling scopes read alphabetically, ahead of scopes their results rest on. In a small blueprint where only zeta is linked, irrationality-proof (Theorems 3-4) comes before the omega results (5-6) that it rests on. No real vault hits this today.
    • Either order unlinked siblings by a subtree-level topological sort, breaking cycles by the current order, or call the fallback id order in the description.
  3. The set of pages in the nav isn't unchanged. Every page its parent doesn't link now joins SUMMARY.md and Previous/Next, after its linked siblings. The published files are the same.
    • Hartshorne gains roadmap/intersection-theory/chow/cycles/exercises/README.md, which cycles/README.md never links. A book with no landing README gains all its pages.
    • Probably wanted, but the description says "The set of pages is unchanged; only the order moves". Say it there and in the CHANGELOG line, which also lacks the PR number.
  4. No test pins SUMMARY.md or Previous/Next to the tree walk. I pasted main's link walk back into _book_page_order and left _reading_pages driving the numbers, the index and Next up. All of tests/test_render.py, test_graph_scale.py, test_skill_examples.py and test_hierarchy_render_regressions.py still pass, although the landing-shortcut book then opens with irrationality-proof again, which is the Hatcher bug this PR fixes.
    • Asserting the SUMMARY.md chapter order and the landing page's Next link in the landing-shortcut variant would catch it.
    • Three other mutants also survive: sorting unlinked children by path, dropping the per-page part of _reading_order's sort key, and removing link reading altogether. The last survives test_book_order_handles_a_1200_page_link_chain, whose pages are now unlinked children of the root, so it no longer covers a link chain.
  5. Docstrings and comments still describe the link walk.
    • _render_summary_nav (autoform_cli/render.py:2186-2188) says the book order follows links, so a page can come before its chapter. With the tree walk, a page always comes after its chapter.
    • In tests/test_render.py, line 1261 says the same, line 1284's "Cohomology is reached before Notes" no longer holds, and line 3097 says irrationality-proof sorts first.
  6. Pre-existing, not from this PR: "Node" numbers skip. Containers and narrative pages take numbers from the same "Node" counter as cards whose declaration has no label. On Hartshorne the first visible Node is now 32; it was 4 on main. Every other label is consecutive. This PR changes only the order of that loop.

Checked and fine

  • The rule. On 22 small blueprints, the walk matches the rule:
    • a child linked twice takes its first link;
    • shortcuts, a missing landing README, unlisted results, part pages and published: false folders behave as described;
    • case-only link differences are ignored as on main.
      An independent implementation of the rule reproduces this head's SUMMARY.md order on all four real sites (3, 6, 40 and 200 pages).
  • One order everywhere.
    • Previous/Next equals SUMMARY.md, which equals the index order of book pages.
    • Cards come in page order, and every label's numbers rise down the book. Main had 8 inversions on zeta(7), 227 on Hatcher and 1,460 on Hartshorne.
    • Next up is the first ready node in index order. Every Kind N (title) cross-reference matches its card.
  • dependencies/index.json.
    • Every integer parent resolves to the right containment parent.
    • Relations are the same multiset as on main, and wiki-link offsets resolve to the same pairs under a matching index_sha256.
    • Node payloads are otherwise identical, there are no new keys, and the explorer decodes the offsets through the index it loads.
  • The description's claims. The bundled example's 7 numbers, SUMMARY.md and Next up are unchanged. Hatcher keeps its page set and now opens with the fundamental group. Hartshorne's outline puts Projective varieties second, and zeta(7)'s Irrationality proof moves to last.
  • Safety and cost. All four sites render with the same pages, counts and warnings on both heads, and every SUMMARY.md parses with literate-nav. Two runs of this head are byte-identical. Hartshorne's render CPU time is within noise of main's: _reading_pages runs twice, at about 5 s under cProfile.
  • Freeze, CI and merge. No new command, flag, JSON key or schema. CI is 9/9 green at 0b2aee5. The branch merges cleanly with Make each statement card its own site search record, with its Lean names #490, which moves each card's anchor onto its heading and touches none of the walk.

Posted by jackmccarthy-77, the reviewer Jack assigned.

- Unlinked siblings start from id order, so ties fall back to id order as
  documented, not to their place in the depth-first topological order.
- When the subtree sort is stuck, release the earliest sibling that sits on
  a cycle, not one that only waits behind it.
- A link to a result inside a chapter counts as a link into that chapter.
- _containment_chain tracks visited nodes in a set, so a deep chain costs
  depth, not depth squared; a 1,200-deep scale test now runs the sibling
  precedence pass.
@Deicyde

Deicyde commented Oct 30, 2026

Copy link
Copy Markdown
Contributor Author

Fixes for the review at 0b2aee57, now at 68c9bde0. The description's "Changes since the review" section has the details.

Notes:

  1. Walk rule (0fca33c5). If a page links a chapter only through its sections, the chapter now reads at the first of those links, and direct links still win. On Hartshorne only positions 191-200 move: étale cohomology now comes before frobenius and the results that rest on it. Hatcher, zeta(7) and the bundled example are byte-identical.
  2. Unlinked siblings (f3bdddb2, acaf886b) now follow a subtree-level topological sort. Ties go to id order, and a cycle is released at its earliest member.
  3. Page-set wording (d192b40d). The description and the CHANGELOG line now say that unlinked pages join SUMMARY.md and Previous/Next, and the line names Read the book in one order: outline, numbers, Next up, Previous/Next and the nav follow the containment tree #495.
  4. Tests (879c2de2) pin SUMMARY.md and the Next links to the walk, and each one kills one of the mutants you listed. A 1,200-deep containment chain replaces the 1,200-page link chain and still exercises link reading.
  5. Docstrings (12eaf0ec). _render_summary_nav and the book test comments now describe the tree walk.
  6. Out of scope, unchanged.

Main is merged (366306af). #490 put data-search-exclude on the bp-thmlabel span, so the numbering test's regex stopped matching and all 6 variants failed against main. The regex now accepts attributes after the class (68c9bde0).

Locally on 68c9bde0, these pass:

  • statement numbers 6/6;
  • book 9, SUMMARY 2, Previous 3 and Next up 5;
  • hierarchy regressions 10 and skill examples 38.

CI is running on the new head.

Posted by PR swarm: PR Swarm Lead

@Deicyde

Deicyde commented Oct 30, 2026

Copy link
Copy Markdown
Contributor Author

#494 landed, so main (8155f232) is merged again at 71ecb546. The only conflict was in _fallback_sections: this PR's helper is kept, with #494's "Other statements" heading.

Local checks on 71ecb546:

  • tests/test_render.py: 315 pass. The five failures are local-only and fail the same way on main here: the publication tests and the tmp-path test.
  • Skill examples 38, hierarchy regressions 10, release 8 and doc budgets 2 all pass.

CI did not start on 68c9bde0, because GitHub can't build a merge ref for a conflicting PR. It should run now.

Posted by PR swarm: PR Swarm Lead

@Deicyde

Deicyde commented Oct 31, 2026

Copy link
Copy Markdown
Contributor Author

Head check at 71ecb54: merge.

This checks only the fixes for the review at 0b2aee57 and the two main merges. All five notes in scope are addressed.

  1. Walk rule (0fca33c5). A child the page links only through its subtree takes the index of its first such link, and a direct link overrides that, as the review proposed. _child_containing stops on a cycle.
  2. Unlinked siblings (f3bdddb2, acaf886b). _sibling_precedence orders two siblings when something in one's subtree depends on something in the other's. The two subtrees are the ones just below the ends' nearest common container. _dependency_sorted is a Kahn sort with ties in id order, and when nothing is ready it releases the earliest sibling that sits on a cycle. Children are now gathered in id order rather than topological order, so the fallback order is the stated one.
  3. Page-set wording (d192b40d): the description and the CHANGELOG line say unlinked pages join the nav, and the line names Read the book in one order: outline, numbers, Next up, Previous/Next and the nav follow the containment tree #495.
  4. Tests (879c2de2). On scratch CI (run), test_render.py, test_graph_scale.py and test_hierarchy_render_regressions.py pass at this head (365 passed, 1 skipped). Each of the review's four mutants now fails a test:
    • main's link walk in _book_page_order fails test_a_chapter_linked_only_through_its_sections_reads_at_that_link;
    • unlinked children sorted by path fails test_unlinked_chapters_read_after_the_chapters_they_rest_on;
    • dropping the page from _reading_order's key fails test_nodes_on_pages_outside_the_book_follow_one_page_at_a_time;
    • removing link reading fails test_the_book_nav_nests_each_page_under_its_folder_whatever_the_book_order.
  5. Docstrings (12eaf0ec): _render_summary_nav and the book test comments describe the tree walk.

Merges. 366306af keeps both new imports in tests/test_render.py. 71ecb546 keeps this PR's _fallback_sections helper with main's "Other statements" heading, and its caller still skips an empty section, as main's inline loop did. 68c9bde0's regex accepts #490's data-search-exclude attribute.

CI passes 9 of 9 at 71ecb54, and the PR merges cleanly with main a87797f.

Posted by Claude Code (QA bot session).

@Deicyde
Deicyde merged commit a920f30 into main Oct 31, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant