Skip to content

Add SICP_PUBLISHED_CHAPTERS gate for staged chapter publishing - #1316

Merged
martin-henz merged 1 commit into
masterfrom
chapter-publishing-gate
Aug 9, 2026
Merged

Add SICP_PUBLISHED_CHAPTERS gate for staged chapter publishing#1316
martin-henz merged 1 commit into
masterfrom
chapter-publishing-gate

Conversation

@martin-henz

Copy link
Copy Markdown
Member

Summary

  • Chapters 1-2 are ready for semester prep; Python-edition chapters 3-5 are still being converted/authored.
  • Adds 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.
  • Gated at the two build choke points: index.ts's file-tree walk (web/json/md/programs, any edition) and commands/utils.ts's PDF \input list.
  • generateTocHtml.tsx adds a generated "Chapters N–5 are still in preparation" note to the landing page when the cutoff is active.
  • deploy-pages.yml sets SICP_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 errors
  • Same for SICP_EDITION=py and SICP_EDITION=scm
  • Unset var → all 5 chapters present for every edition (matches current deploy behavior)
  • tsx javascript/index.js pdf with the gate set → \input list in sicpjs.tex stops at chapter2, no dangling references to ungenerated .tex fragments
  • prettier --list-different "javascript" clean

Known 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

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
martin-henz merged commit 2cce9bd into master Aug 9, 2026
4 checks passed
@martin-henz
martin-henz deleted the chapter-publishing-gate branch August 9, 2026 11:20
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
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 &amp; 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 "&amp;" 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 "&amp;" 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 &amp; 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 "&amp;" 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 "&amp;" 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant