Add SICP_PUBLISHED_CHAPTERS gate for staged chapter publishing - #1316
Merged
Conversation
Only chapters 1-2 are ready for the Python edition this semester; the env var lets chapter3-5 stay authored, tested, and readable locally while excluded from the deployed site (PDF, split HTML, JSON, Markdown, programs). Unset (the default everywhere except deploy) publishes everything. deploy-pages.yml sets it only on the Python build step, so promoting a chapter is a one-line bump there. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KAAMFom423g6SSSrgsMxGC
martin-henz
added a commit
that referenced
this pull request
Aug 9, 2026
…ntime (#1317) The interactive site's table of contents wasn't documented anywhere, so nobody noticed it needed to stay in sync with the published chapter range (SICP_PUBLISHED_CHAPTERS, #1316). The frontend has since been fixed to fetch these at runtime instead of snapshotting them. Claude-Session: https://claude.ai/code/session_01RsEaJ8SQRH4QkXir4JSs4S Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
martin-henz
added a commit
to source-academy/frontend
that referenced
this pull request
Aug 9, 2026
#4273) * Fetch SICPy TOC dynamically instead of snapshotting it; friendlier 404 message Two teething problems surfaced after source-academy/sicp#1316 gated SICPy's published chapters down to 1-2: - The table of contents still listed chapters 3-5, because it was a static JSON snapshot (src/features/textbook/toc/data/*.json) committed to this repo instead of being fetched from the deployed site at runtime, the same way section content and search data already are. It never got resynced when the gate merged, and nothing would have caught a future desync either. Both editions' TOC and prev/next navigation now come from json[_py]/toc.json and toc-navigation.json via react-query, so this can't go stale again. - Clicking any gated section crashed into a generic "unexpected error" instead of a 404 message: GitHub Pages serves over HTTP/2, where the Fetch API's res.statusText is always blank (no reason phrase), so the `res.statusText === 'Not Found'` check never matched. 404s are now detected via res.status, and route to a new, friendlier "This section is in preparation" message instead of the generic error screen. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RsEaJ8SQRH4QkXir4JSs4S * Surface a message when the SICP TOC fetch itself fails The tocJs/tocPy/tocNavigationJs/tocNavigationPy queries fell back to [] / {} for both the loading and error states, so a genuine fetch failure (as opposed to still loading) silently rendered an empty table of contents with no indication anything went wrong. Now surfaces a short message in the two places readers actually look at the TOC as content (the index page's Contents list and the navbar drawer). Left the prev/next controls (navbar arrows and the per-section Previous/Next buttons) as graceful degradation: an unresolved neighbour there just looks like a first/last page, which is the same UI state as the existing "no next page" case and doesn't block reading the page itself. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RsEaJ8SQRH4QkXir4JSs4S --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
martin-henz
added a commit
that referenced
this pull request
Aug 9, 2026
Chapter/section/exercise/figure/footnote references from a published chapter into a later, still-gated one (SICP_PUBLISHED_CHAPTERS, #1316) showed as "Chapter" with no number in the interactive site and "??" in the PDF, because labels are only ever registered for chapters the build actually visits, and unpublished chapters are skipped entirely. json: label/exercise/figure/footnote registration now runs over the *entire* book unconditionally (see forceIncludeAllChapters), so a forward reference resolves to a real number and href; only that resolution is kept; unpublished chapters' actual page content is still never written to json_<lang>/. The href just 404s into "this section is in preparation" until that chapter is published, same as any other unpublished link. pdf: chapters are now brought in with \include instead of \input, and every chapter's .tex fragment is generated unconditionally (gating moves entirely to \includeonly). \include reads every listed chapter's .aux regardless of \includeonly, so scripts/do.sh now does a two-pass compile when SICP_PUBLISHED_CHAPTERS is set: a throwaway "shadow" pass typesets every chapter once to populate each one's .aux with real label numbers, then the real "publish" pass restricts \includeonly to the actually-published subset, resolving forward \ref/\pageref against the .aux the shadow pass left behind. Verified locally: SICPy's forward references (chap:state -> 3, chap:meta -> 4, plus section/figure/exercise/footnote refs) all resolve to real numbers/hrefs in json_py, and the compiled sicpy.pdf shows "Chapter 3"/"Chapter 4" with zero "??" across all 229 pages. SICP JS (unaffected edition, no chapters ever gated) still builds cleanly at 640 pages with the \include switch. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RsEaJ8SQRH4QkXir4JSs4S
5 tasks
martin-henz
added a commit
that referenced
this pull request
Aug 9, 2026
…1318) * Document that the frontend fetches toc.json/toc-navigation.json at runtime The interactive site's table of contents wasn't documented anywhere, so nobody noticed it needed to stay in sync with the published chapter range (SICP_PUBLISHED_CHAPTERS, #1316). The frontend has since been fixed to fetch these at runtime instead of snapshotting them. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RsEaJ8SQRH4QkXir4JSs4S * Fix literal & showing up in the interactive editions <AMP/> in the XML source (used because a literal & can't appear raw in XML text) was translated to the JSON output as the HTML entity string "&" rather than the character "&". That's correct for an HTML sink, but the frontend renders JSON #text nodes as plain React text (ParseJson.tsx's handleText), which doesn't decode HTML entities, so readers saw the literal string "&" instead of "&". Affects any <AMP/> in the XML sources, currently: the SICPy CWI mention in chapter1.xml, and two References citations (Wiley & Sons, Harper & Row) shared by both SICP JS and SICPy. The PDF is unaffected — it goes through the separate LaTeX converter (parseXmlLatex.js), which never called this function. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RsEaJ8SQRH4QkXir4JSs4S * Resolve forward references into not-yet-published chapters Chapter/section/exercise/figure/footnote references from a published chapter into a later, still-gated one (SICP_PUBLISHED_CHAPTERS, #1316) showed as "Chapter" with no number in the interactive site and "??" in the PDF, because labels are only ever registered for chapters the build actually visits, and unpublished chapters are skipped entirely. json: label/exercise/figure/footnote registration now runs over the *entire* book unconditionally (see forceIncludeAllChapters), so a forward reference resolves to a real number and href; only that resolution is kept; unpublished chapters' actual page content is still never written to json_<lang>/. The href just 404s into "this section is in preparation" until that chapter is published, same as any other unpublished link. pdf: chapters are now brought in with \include instead of \input, and every chapter's .tex fragment is generated unconditionally (gating moves entirely to \includeonly). \include reads every listed chapter's .aux regardless of \includeonly, so scripts/do.sh now does a two-pass compile when SICP_PUBLISHED_CHAPTERS is set: a throwaway "shadow" pass typesets every chapter once to populate each one's .aux with real label numbers, then the real "publish" pass restricts \includeonly to the actually-published subset, resolving forward \ref/\pageref against the .aux the shadow pass left behind. Verified locally: SICPy's forward references (chap:state -> 3, chap:meta -> 4, plus section/figure/exercise/footnote refs) all resolve to real numbers/hrefs in json_py, and the compiled sicpy.pdf shows "Chapter 3"/"Chapter 4" with zero "??" across all 229 pages. SICP JS (unaffected edition, no chapters ever gated) still builds cleanly at 640 pages with the \include switch. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RsEaJ8SQRH4QkXir4JSs4S --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
martin-henz
added a commit
that referenced
this pull request
Aug 9, 2026
* Document that the frontend fetches toc.json/toc-navigation.json at runtime The interactive site's table of contents wasn't documented anywhere, so nobody noticed it needed to stay in sync with the published chapter range (SICP_PUBLISHED_CHAPTERS, #1316). The frontend has since been fixed to fetch these at runtime instead of snapshotting them. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RsEaJ8SQRH4QkXir4JSs4S * Fix literal & showing up in the interactive editions <AMP/> in the XML source (used because a literal & can't appear raw in XML text) was translated to the JSON output as the HTML entity string "&" rather than the character "&". That's correct for an HTML sink, but the frontend renders JSON #text nodes as plain React text (ParseJson.tsx's handleText), which doesn't decode HTML entities, so readers saw the literal string "&" instead of "&". Affects any <AMP/> in the XML sources, currently: the SICPy CWI mention in chapter1.xml, and two References citations (Wiley & Sons, Harper & Row) shared by both SICP JS and SICPy. The PDF is unaffected — it goes through the separate LaTeX converter (parseXmlLatex.js), which never called this function. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RsEaJ8SQRH4QkXir4JSs4S * Resolve forward references into not-yet-published chapters Chapter/section/exercise/figure/footnote references from a published chapter into a later, still-gated one (SICP_PUBLISHED_CHAPTERS, #1316) showed as "Chapter" with no number in the interactive site and "??" in the PDF, because labels are only ever registered for chapters the build actually visits, and unpublished chapters are skipped entirely. json: label/exercise/figure/footnote registration now runs over the *entire* book unconditionally (see forceIncludeAllChapters), so a forward reference resolves to a real number and href; only that resolution is kept; unpublished chapters' actual page content is still never written to json_<lang>/. The href just 404s into "this section is in preparation" until that chapter is published, same as any other unpublished link. pdf: chapters are now brought in with \include instead of \input, and every chapter's .tex fragment is generated unconditionally (gating moves entirely to \includeonly). \include reads every listed chapter's .aux regardless of \includeonly, so scripts/do.sh now does a two-pass compile when SICP_PUBLISHED_CHAPTERS is set: a throwaway "shadow" pass typesets every chapter once to populate each one's .aux with real label numbers, then the real "publish" pass restricts \includeonly to the actually-published subset, resolving forward \ref/\pageref against the .aux the shadow pass left behind. Verified locally: SICPy's forward references (chap:state -> 3, chap:meta -> 4, plus section/figure/exercise/footnote refs) all resolve to real numbers/hrefs in json_py, and the compiled sicpy.pdf shows "Chapter 3"/"Chapter 4" with zero "??" across all 229 pages. SICP JS (unaffected edition, no chapters ever gated) still builds cleanly at 640 pages with the \include switch. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RsEaJ8SQRH4QkXir4JSs4S * Fix SICPy code snippets opening the evaluator on the wrong chapter Clicking a code snippet in the interactive SICPy site always opened the embedded evaluator on Python §1, regardless of which chapter the snippet was actually from (e.g. a chapter-2 snippet still opened Python §1, not Python §2). The snippet's embedded-evaluator hash was built with the Source (JavaScript) chap=/variant= scheme for every edition. That's correct for SICP JS, whose chapters are part of the js-slang Chapter enum, but Python isn't part of that enum -- it's a Conductor-based language directory entry (python1..python4) addressed via language=/variant= instead (see frontend's Playground.tsx: handleHash's `if (chapter)` branch vs `else if (qs.language)`). Sending chap=2 for Python content left the evaluator on whatever chapter it already had open, since chap= isn't meaningful to a non-Source language. Verified against a local frontend build (patching a fetched section's snippet hash in-browser): opening a chapter-2 snippet now correctly shows "Python §2" and the banner reads "You have chosen Python §2, matching SICPy §2" instead of §1. SICP JS's chap=/variant= hash is unchanged. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RsEaJ8SQRH4QkXir4JSs4S --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
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.
Summary
SICP_PUBLISHED_CHAPTERS(a chapter-number cutoff): unset (the default everywhere except deploy) publishes everything, so local builds/tests/reads of chapters 3-5 are unaffected.index.ts's file-tree walk (web/json/md/programs, any edition) andcommands/utils.ts's PDF\inputlist.generateTocHtml.tsxadds a generated "Chapters N–5 are still in preparation" note to the landing page when the cutoff is active.deploy-pages.ymlsetsSICP_PUBLISHED_CHAPTERS: "2"only on the Python edition build step — JS and Scheme are already complete and keep publishing every chapter. The mechanism itself is edition-agnostic; promoting a chapter later is a one-line bump of that number.Test plan
SICP_PUBLISHED_CHAPTERS=2 tsx javascript/index.js json(JS edition) → toc stops at chapter 2, no missing-file errorsSICP_EDITION=pyandSICP_EDITION=scmtsx javascript/index.js pdfwith the gate set →\inputlist insicpjs.texstops at chapter2, no dangling references to ungenerated.texfragmentsprettier --list-different "javascript"cleanKnown side effect
Forward cross-references from a published chapter into a still-gated one (e.g. chapter 2 referencing something defined in chapter 3) produce a "REF not found" build warning and may render as a broken link until that chapter is promoted.
🤖 Generated with Claude Code
https://claude.ai/code/session_01KAAMFom423g6SSSrgsMxGC