Skip to content

render: graph and chapter pages still resolve every target page, keeping render quadratic #93

Closed

Description

@Deicyde

Problem

#88 links each target page once per map instead of once per node, but four call sites still build the full node-id to href map for every page they write:

  • write_graph_pages: the chapter, scope and focus loops, each through the node_links callback that render_site passes in
  • _render_chapter

Each full map costs about three Path.resolve calls per distinct target page: one in _anchored_links, two in mermaid.relative_link. When the number of target pages grows with the blueprint, as it does in sectioned layouts, flat layouts with many chapters, and real textbooks, render stays quadratic.

Path.resolve calls per render with #88, on generated roadmaps of 4 chapters with 5, 10, 20 or 40 sections each, 2 theorems per section, chained across the chapter:

nodes target pages Path.resolve calls
65 25 8,241
125 45 25,801
245 85 89,721
485 165 332,761

Each doubling multiplies the count by 3.1, 3.5, then 3.7.

On a 1,791-node textbook blueprint with 199 distinct target pages, #88 takes render from 1,540 s to 73 s CPU with identical output. 87% of the remaining CPU is in _anchored_links, almost all of it under node_links and _render_chapter.

Removing the repeated resolves doesn't need the node_links signature change that #87 and #88 set aside. Memoizing resolve() results and hrefs per (target page, page directory) inside the node_links closure took the same blueprint from 73 s to 20 s CPU, with byte-identical output. The closure alone leaves the 485-node roadmap at 92,356 resolves, 79,040 of them in _render_chapter; sharing the memo with every _anchored_links caller brings it to 12,326 and makes growth linear.

Acceptance criteria

  • Make Path.resolve calls grow linearly with blueprint size: on the roadmaps above, doubling the sections should at most double the calls.
  • Keep rendered output byte-identical.
  • Add a scale regression that counts Path.resolve calls at two sizes of a sectioned blueprint and fails before the fix.

Draft #90 removes the same cost another way: it drops the focus pages, passes node_links only the article ids each view shows, and resolves links lazily. That brings the 485-node roadmap to 16,186 resolves, with different output by design. If #90 lands first, only the scale regression is left here.

Repeated Graph.children scans and whole-graph index rebuilds are tracked in #60.

Activity

  1. added
    has PRAn open pull request addresses this issue
    on Oct 13, 2026
  2. Deicyde commented on Oct 13, 2026

    @Deicyde
    ContributorAuthor

    Draft #90 removes the quadratic link resolution another way: links resolve lazily, each view resolves only its own articles, and the focus pages are gone. It does not add the two-size Path.resolve scale regression asked for here, and its output is intentionally not byte-identical.

    Posted by issue triage

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requesthas PRAn open pull request addresses this issue

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions