From 18e9c0ad24517f915bae7b8401ca65b6654e552d Mon Sep 17 00:00:00 2001 From: Jeremy Manning Date: Sat, 10 Oct 2026 11:33:22 -0400 Subject: [PATCH 1/3] docs: gallery notebooks require this release and play showcase animations Two problems reported from Colab right after 1.1.0: - The install cell was '%pip install -q "hypertools[interactive]"'. A runtime that already had 1.0.0 reported it satisfied and the 1.1 examples failed ("Normalize.__init__() got an unexpected keyword argument 'mode'"). The generated cell now carries '>=', and the release checker rejects an install line without a floor. - Six showcase examples end with 'fig = anim.figure' inside the main block, which displays nothing in a notebook. The docs build now appends a final 'anim' cell to those notebooks, rendered inline by HyperAnimation. The published v1.1.0 notebooks on docs-notebooks were patched the same way. Co-Authored-By: Claude Opus 5.5 --- RELEASE_CHECKLIST.md | 9 +- docs/_gallery_notebooks.py | 62 ++++++++++ docs/conf.py | 21 +++- notes/session_2026-10-08_visual-signoff.md | 26 +++++ scripts/add_colab_install_cell.py | 16 ++- scripts/check_release_notebooks.py | 25 +++- tests/test_gallery_notebook_playback.py | 128 +++++++++++++++++++++ tests/test_release_notebook_check.py | 40 ++++++- 8 files changed, 319 insertions(+), 8 deletions(-) create mode 100644 docs/_gallery_notebooks.py create mode 100644 tests/test_gallery_notebook_playback.py diff --git a/RELEASE_CHECKLIST.md b/RELEASE_CHECKLIST.md index e6a1330b..49d3bfb9 100644 --- a/RELEASE_CHECKLIST.md +++ b/RELEASE_CHECKLIST.md @@ -122,9 +122,12 @@ detached tag checkout — the notebook migrator detects the branch via `python scripts/publish_gallery_notebooks.py --ref v1.1.0 --notebooks-dir docs/auto_examples --push` (the `docs-notebooks` branch already exists; the script deletes and rewrites the whole `v1.1.0/` namespace and its `manifest.json`, so a - re-cut republishes cleanly over the draft's publish). Publishing static - notebooks before PyPI is harmless: their `%pip install hypertools[...]` - cells resolve 1.1 the moment PyPI is updated (step 6). + re-cut republishes cleanly over the draft's publish). The notebooks' + install cells carry a version floor (`hypertools[...]>=1.1.0`), so a + runtime opened before the PyPI upload (step 6) fails to install rather + than quietly keeping 1.0.0; do not test them on Colab until PyPI has + 1.1.0, and after the upload run one gallery notebook and one tutorial + on Colab in a fresh runtime. Both the `master` "latest" docs and the `v1.1.0` "stable" docs resolve to this ONE `v1.1.0` namespace (`docs/post_build.py` `_publish_ref`), so this single publish covers both. diff --git a/docs/_gallery_notebooks.py b/docs/_gallery_notebooks.py new file mode 100644 index 00000000..c8d87707 --- /dev/null +++ b/docs/_gallery_notebooks.py @@ -0,0 +1,62 @@ +"""Post-processing for the notebooks sphinx-gallery generates. + +Kept in its own SIDE-EFFECT-FREE module (standard library only) so +tests/test_gallery_notebook_playback.py can unit-test it. + +Six animated showcase examples build their animation inside +``if __name__ == '__main__':`` and end with ``fig = anim.figure``, so that +importing the example (the test suite does) builds nothing. The docs page +still shows the animation, because the docs build captures it. A notebook +shows nothing: an assignment inside an ``if`` block is not a cell value. +``add_inline_playback`` appends a final cell holding ``anim``, which Jupyter +and Colab render as an inline video through ``HyperAnimation._repr_html_``. +""" +import copy +import json +import re + +PLAYBACK_SOURCE = ( + '# Play the animation inline. The whole clip is rendered first, which\n' + '# takes from a few seconds to a few minutes, depending on its length.\n' + 'anim') +_ENDS_WITH_UNSHOWN_ANIMATION = re.compile(r'(?m)^[ \t]+fig = anim\.figure[ \t]*$') + + +def needs_inline_playback(nb): + """True when the notebook's last code cell ends by binding + ``fig = anim.figure`` inside a block, i.e. it built an animation and + displayed nothing.""" + code = [c for c in nb.get('cells', []) if c.get('cell_type') == 'code'] + if not code: + return False + source = code[-1]['source'] + source = ''.join(source) if isinstance(source, list) else source + lines = [ln for ln in source.splitlines() if ln.strip()] + return bool(lines) and bool(_ENDS_WITH_UNSHOWN_ANIMATION.match(lines[-1])) + + +def add_inline_playback(path): + """Append the playback cell to the notebook at ``path`` if it needs one. + Returns True when the file was changed. Idempotent: once the cell is + there, the last code cell no longer ends with the assignment.""" + with open(path, encoding='utf-8') as f: + raw = f.read() + nb = json.loads(raw) + if not needs_inline_playback(nb): + return False + template = [c for c in nb['cells'] if c['cell_type'] == 'code'][-1] + cell = copy.deepcopy(template) + cell['outputs'] = [] + cell['execution_count'] = None + cell['source'] = PLAYBACK_SOURCE.splitlines(keepends=True) + nb['cells'].append(cell) + # keep the file's own JSON layout so the change is one appended cell + tail = '\n' if raw.endswith('\n') else '' + original = json.loads(raw) + layout = next(((i, a) for i in (1, 2, None) for a in (False, True) + if json.dumps(original, indent=i, ensure_ascii=a) + tail + == raw), (1, False)) + with open(path, 'w', encoding='utf-8') as f: + f.write(json.dumps(nb, indent=layout[0], ensure_ascii=layout[1]) + + tail) + return True diff --git a/docs/conf.py b/docs/conf.py index 2515cbc9..682cddc7 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -83,7 +83,13 @@ def _install_notebook_cell(): # master or a vX.Y.Z release tag -> the released package; anything else # (dev-1.0, feature branches) -> that branch from GitHub as a preview. if branch == 'master' or re.fullmatch(r'v\d+\.\d+\.\d+', branch): - pip = '%pip install -q "hypertools[interactive]"' + # the floor matters: without it, a runtime that already has an older + # hypertools (a Colab session opened before this release reached + # PyPI, or any environment with the previous release) reports the + # requirement satisfied and the examples run against the old version + import hypertools + pip = ('%pip install -q "hypertools[interactive]>=' + f'{hypertools.__version__}"') note = '# Install hypertools (run this first on Colab)' else: url = 'git+https://github.com/ContextLab/hypertools.git@' + branch @@ -570,6 +576,16 @@ def _gallery_page_context(app, pagename, templatename, context, doctree): context['theme_source_view_link'] = _GALLERY_VIEW_LINK +def _add_inline_playback_cells(app): + """Give the animated showcase notebooks a final cell that plays the + animation (docs/_gallery_notebooks.py says why they need one).""" + import glob + from _gallery_notebooks import add_inline_playback + for path in sorted(glob.glob(os.path.join(app.srcdir, 'auto_examples', + '*.ipynb'))): + add_inline_playback(path) + + def setup(app): app.connect('html-page-context', _gallery_page_context) # Keep the strict (-W) docs-clean CI gate robust to TRANSIENT third-party @@ -581,3 +597,6 @@ def setup(app): # docs/_gallery_log_filter.py and tests/test_docs_gallery_log_filter.py. from _gallery_log_filter import install install() + # sphinx-gallery writes the notebooks in its own 'builder-inited' + # handler (default priority 500); run after it + app.connect('builder-inited', _add_inline_playback_cells, priority=900) diff --git a/notes/session_2026-10-08_visual-signoff.md b/notes/session_2026-10-08_visual-signoff.md index 529bfc7c..aee97a8f 100644 --- a/notes/session_2026-10-08_visual-signoff.md +++ b/notes/session_2026-10-08_visual-signoff.md @@ -243,3 +243,29 @@ Branch `fix/1.1-release-review`; PR #286 still OPEN and unmerged. Nothing merged - Parallel gallery trial (6 workers, scratch clone, while the serial build ran): 12.9 min, exit 0 with -W. animate_weather_decades (696 s) and animate_market_sectors (573 s) bound it. One loky "worker stopped" notice. Not adopted for 1.1.0. + +## 2026-10-10: release day and the first post-release report + +- 12:10 UTC: `v1.1.0` tag moved to cf76a931 (master CI 17/17 green at 08:15; four idle hours because my + waiter watched the "Dependency Graph" run id instead of the "Tests" run). Tag CI 17/17 green 14:11. + Each CI run takes ~2 h because the ubuntu-3.12 job runs the suite three times serially. +- Jeremy: "Can you do steps 1--3, and also do the conda forge release? I'll post the bluesky thread". + RTD `stable` re-synced by redelivering the last master push webhook delivery (RTD ignores a force-moved + tag); PyPI 1.1.0 uploaded 14:40 (digests match; clean-env wheel smoke OK); GitHub release published + 14:42; conda-forge/hypertools-feedstock#1 opened from the jeremymanning fork, rerendered, merged 14:51, + package uploaded (CDN index lag; clean conda install not yet confirmed). +- First report after release (Jeremy, Colab, gallery notebook animate_morph_zoo): (1) TypeError + "Normalize.__init__() got an unexpected keyword argument 'mode'": a runtime opened before the PyPI + upload had hypertools 1.0.0 and the unfloored `%pip install -q "hypertools[interactive]"` was "already + satisfied". Deleting the runtime fixed it. (2) the notebook showed no figure: six showcase examples end + with `fig = anim.figure` inside `if __name__ == '__main__':`, which displays nothing in a notebook. +- Fixed in the PUBLISHED notebooks (docs-notebooks branch, no change to master or the tag): all 51 install + cells now read `hypertools[interactive]>=1.1.0` (Jeremy: keep it); the six showcases have a final cell + `anim` (Jeremy chose "Play the animation inline"). Verified on Colab: morph zoo plays a 30 s video, + rendered in 48 s. Measured inline render with the PyPI package, locally: morph 4 s, forecast 9 s, + conversation 19 s, paintings 52 s, market 74 s, weather 235 s; none exceeds the embed limit. +- Source fix for the next release is on branch `fix/gallery-install-floor` (NOT master: the release gate + allows no commit there between releases): conf.py emits the floored install line and appends the + playback cell after sphinx-gallery writes the notebooks (docs/_gallery_notebooks.py); + check_release_notebooks.py rejects an unfloored install; tests added. What we missed: no gallery + notebook was run on Colab against the published package; the checklist now says to. diff --git a/scripts/add_colab_install_cell.py b/scripts/add_colab_install_cell.py index f5486066..3f33ecfe 100644 --- a/scripts/add_colab_install_cell.py +++ b/scripts/add_colab_install_cell.py @@ -5,7 +5,7 @@ Gallery install lines are branch-aware: * on ``master`` OR a ``vX.Y.Z`` release tag it installs the RELEASED package - (``%pip install -q "hypertools[interactive]"``); + (``%pip install -q "hypertools[interactive]>="``); * on any other branch it installs THAT branch from GitHub, so the dev-1.0 preview notebooks install the matching dev build rather than the older PyPI release. @@ -121,9 +121,21 @@ def portable_video_source(filename): return '# Colab serves output frames separately from kernel files; embed movie bytes.\ntry:\n from google import colab as colab\nexcept ImportError:\n pass # Local Jupyter/Sphinx uses the relative video below.\nelse:\n from IPython.display import Video, display\n display(Video({filename!r}, embed=True))\n'.format(filename=filename) +def _project_version(): + """The release version, from pyproject.toml (the gallery install cell's + floor; docs/conf.py uses ``hypertools.__version__``, the same number).""" + root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + with open(os.path.join(root, 'pyproject.toml'), encoding='utf-8') as f: + return re.search(r'(?m)^version\s*=\s*["\']([^"\']+)["\']', + f.read()).group(1) + + def install_lines(branch): if _is_release_ref(branch): - pip = '%pip install -q "hypertools[interactive]"' + # floored: an environment that already has an older hypertools would + # otherwise satisfy the requirement and run the examples against it + pip = ('%pip install -q "hypertools[interactive]>=' + f'{_project_version()}"') note = '# Install hypertools (run this first on Colab)' else: pip = f'%pip install -q "{hyp_spec("interactive", branch)}"' diff --git a/scripts/check_release_notebooks.py b/scripts/check_release_notebooks.py index 5f0ec129..32ab9b4e 100644 --- a/scripts/check_release_notebooks.py +++ b/scripts/check_release_notebooks.py @@ -91,6 +91,25 @@ def classify_notebooks(paths): return missing, branch_installs, stale_notes +_FLOORED_RE = re.compile(r'hypertools\[[^\]]*\]\s*>=\s*\d') + + +def unfloored_installs(paths): + """Notebooks with a hypertools install line that carries no version + floor (``hypertools[...]>=X.Y.Z``). Without one, ``pip install`` in an + environment that already has an OLDER hypertools reports the requirement + satisfied, and a notebook written for this release runs against the old + one (seen on Colab: a session opened before 1.1.0 reached PyPI kept 1.0.0 + and the 1.1 examples failed with a TypeError).""" + names = [] + for p in paths: + with open(p, encoding='utf-8') as f: + nb = json.load(f) + if any(not _FLOORED_RE.search(ln) for ln, _ in _hyp_install_lines(nb)): + names.append(os.path.basename(p)) + return names + + def _collect(dirs_or_globs): paths = [] for d in dirs_or_globs: @@ -122,7 +141,8 @@ def main(argv=None): file=sys.stderr) return 1 missing, branch, stale = classify_notebooks(paths) - if not (missing or branch or stale): + unfloored = unfloored_installs(paths) + if not (missing or branch or stale or unfloored): print(f'release notebook check OK: {len(paths)} notebooks, ' 'all install the PyPI package') return 0 @@ -132,6 +152,9 @@ def main(argv=None): print(f'FAIL git+/@branch install: {branch}', file=sys.stderr) if stale: print(f'FAIL leftover preview note: {stale}', file=sys.stderr) + if unfloored: + print(f'FAIL hypertools install without a version floor (>=X.Y.Z): ' + f'{unfloored}', file=sys.stderr) return 1 diff --git a/tests/test_gallery_notebook_playback.py b/tests/test_gallery_notebook_playback.py new file mode 100644 index 00000000..b9140008 --- /dev/null +++ b/tests/test_gallery_notebook_playback.py @@ -0,0 +1,128 @@ +"""The animated showcase notebooks must display their animation. + +Six gallery examples build the animation inside ``if __name__ == +'__main__':`` and end with ``fig = anim.figure``. The docs page shows the +animation because the docs build captures it, but the generated notebook +displayed nothing (reported from Colab right after 1.1.0). The docs build now +appends a final ``anim`` cell (docs/_gallery_notebooks.py). +""" +import importlib.util +import json +import pathlib + +import pytest + +_REPO = pathlib.Path(__file__).resolve().parent.parent +_SHOWCASES = ('animate_morph_zoo', 'animate_conversation', + 'animate_painting_embeddings', 'animate_forecast', + 'animate_market_sectors', 'animate_weather_decades') + +pytestmark = pytest.mark.skipif( + not (_REPO / 'docs' / '_gallery_notebooks.py').is_file(), + reason='requires a source checkout (docs/)') + + +def _load(): + spec = importlib.util.spec_from_file_location( + '_gallery_notebooks', _REPO / 'docs' / '_gallery_notebooks.py') + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + +gnb = _load() if (_REPO / 'docs' / '_gallery_notebooks.py').is_file() else None + + +def _notebook(*code, indent=2): + cells = [{'cell_type': 'markdown', 'metadata': {}, 'source': ['# Title']}] + cells += [{'cell_type': 'code', 'execution_count': None, + 'metadata': {'collapsed': False}, 'outputs': [], + 'source': src.splitlines(keepends=True)} for src in code] + return json.dumps({'cells': cells, 'metadata': {}, 'nbformat': 4, + 'nbformat_minor': 0}, indent=indent) + + +_UNSHOWN = ("import hypertools as hyp\n\nif __name__ == '__main__':\n" + " anim = construct_artifact(shapes)\n fig = anim.figure\n") + + +def test_a_notebook_ending_with_the_unshown_animation_gets_a_playback_cell( + tmp_path): + path = tmp_path / 'animate_x.ipynb' + path.write_text(_notebook('%pip install -q "hypertools[interactive]"', + _UNSHOWN), encoding='utf-8') + assert gnb.add_inline_playback(str(path)) is True + nb = json.loads(path.read_text(encoding='utf-8')) + last = nb['cells'][-1] + assert last['cell_type'] == 'code' and last['outputs'] == [] + lines = [ln for ln in ''.join(last['source']).splitlines() + if ln.strip() and not ln.lstrip().startswith('#')] + assert lines == ['anim'] # a bare expression: the cell's value + # the earlier cells are untouched + assert ''.join(nb['cells'][-2]['source']) == _UNSHOWN + + +def test_adding_the_cell_twice_changes_nothing(tmp_path): + path = tmp_path / 'animate_x.ipynb' + path.write_text(_notebook(_UNSHOWN), encoding='utf-8') + assert gnb.add_inline_playback(str(path)) is True + once = path.read_bytes() + assert gnb.add_inline_playback(str(path)) is False + assert path.read_bytes() == once + + +@pytest.mark.parametrize('source', [ + "import hypertools as hyp\nhyp.plot(data, animate=True)\n", # shows itself + "fig = anim.figure\n", # top level: not the pattern + " fig = anim.figure\nprint('saved')\n", # something follows it +]) +def test_other_notebooks_are_left_alone(tmp_path, source): + path = tmp_path / 'plot_x.ipynb' + path.write_text(_notebook(source), encoding='utf-8') + before = path.read_bytes() + assert gnb.add_inline_playback(str(path)) is False + assert path.read_bytes() == before + + +@pytest.mark.parametrize('indent', [1, 2]) +def test_the_files_json_layout_is_kept(tmp_path, indent): + path = tmp_path / 'animate_x.ipynb' + path.write_text(_notebook(_UNSHOWN, indent=indent), encoding='utf-8') + gnb.add_inline_playback(str(path)) + text = path.read_text(encoding='utf-8') + assert text == json.dumps(json.loads(text), indent=indent) + + +@pytest.mark.parametrize('stem', _SHOWCASES) +def test_the_showcase_examples_still_end_the_way_the_cell_is_keyed_on(stem): + # if an example stops ending with `fig = anim.figure`, its notebook + # silently loses the playback cell: fail here instead + source = (_REPO / 'examples' / f'{stem}.py').read_text(encoding='utf-8') + nb = {'cells': [{'cell_type': 'code', 'source': source}]} + assert gnb.needs_inline_playback(nb), stem + + +def test_the_playback_cell_really_displays_a_video(): + # the cell relies on HyperAnimation._repr_html_; exercise it for real + import numpy as np + import hypertools as hyp + rng = np.random.default_rng(0) + anim = hyp.plot([rng.standard_normal((30, 3)) for _ in range(2)], + animate='morph', duration=1, frame_rate=5, show=False, + backend='matplotlib') + html = anim._repr_html_() + assert html and ('=1.1.0"')) + assert crn.unfloored_installs([bare, floored]) == ['bare.ipynb'] + assert crn.main([bare]) == 1 + assert crn.main([floored]) == 0 + + +def test_every_install_line_needs_the_floor(tmp_path): + p = _write(tmp_path, 'mixed.ipynb', _nb( + '%pip install -q "hypertools[interactive]>=1.1.0"', + '%pip install -q "hypertools[text]"')) + assert crn.unfloored_installs([p]) == ['mixed.ipynb'] + + +def test_the_generated_gallery_cell_and_the_tutorials_carry_the_floor(): + import glob + import importlib.util + import re + repo = _SCRIPT.parent.parent + spec = importlib.util.spec_from_file_location( + 'add_colab_install_cell', repo / 'scripts' / 'add_colab_install_cell.py') + acic = importlib.util.module_from_spec(spec) + spec.loader.exec_module(acic) + _note, pip = acic.install_lines('master') + version = re.search(r'(?m)^version\s*=\s*"([^"]+)"', + (repo / 'pyproject.toml').read_text()).group(1) + assert pip == f'%pip install -q "hypertools[interactive]>={version}"' + tutorials = sorted(glob.glob(str(repo / 'docs' / 'tutorials' / '*.ipynb'))) + assert len(tutorials) >= 20 + assert crn.unfloored_installs(tutorials) == [] From 2765df086b3491bdff3b1ba47e3148fb6ac1883a Mon Sep 17 00:00:00 2001 From: Jeremy Manning Date: Sat, 10 Oct 2026 11:38:18 -0400 Subject: [PATCH 2/3] ci: the ubuntu 3.12 job runs the suite once, not three times It ran plain pytest, then the pandas-3 acceptance gate (a second full run), then pytest with coverage (a third): 36 + 36 + 50 minutes, which made every CI run take two hours. The pandas-3 gate and the coverage run are now one pass, and that pass is the matrix cell's test run. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/test.yml | 22 +++++++++++----------- RELEASE_CHECKLIST.md | 6 +++++- notes/session_2026-10-08_visual-signoff.md | 6 ++++++ 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b2af6bd7..eb4b6844 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -144,6 +144,11 @@ jobs: python -c "import matplotlib.font_manager as fm; fm._load_fontmanager(try_read_cache=False)" - name: Run pytest + # Ubuntu / Python 3.12 runs the suite ONCE, in the pandas-3 + coverage + # step below. It used to run it three times in a row (here, then the + # pandas-3 gate, then coverage: 36 + 36 + 50 minutes), which made that + # one job, and so every CI run, take two hours. + if: ${{ !(matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12') }} env: MPLBACKEND: Agg # Authenticate the fivethirtyeight loader's GitHub API listing calls @@ -156,24 +161,19 @@ jobs: run: | pytest -v --tb=short - - name: Acceptance gate — pandas 3.0 (Ubuntu Python 3.12 only) - # Guarantees a pandas-3 run regardless of resolver choices (dw#30/#31 fix). + - name: Run pytest — pandas 3.0 acceptance gate, with coverage (Ubuntu Python 3.12 only) + # This matrix cell's one test run. It guarantees a pandas-3 run + # regardless of resolver choices (dw#30/#31 fix) and produces the + # coverage report in the same pass. if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12' env: MPLBACKEND: Agg + # see "Run pytest" above for why the token is passed GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | pip install "pandas>=3.0" python -c "import pandas as pd; assert pd.__version__.split('.')[0] == '3', pd.__version__; print('pandas', pd.__version__)" - pytest -q - - - name: Run pytest with coverage (Ubuntu Python 3.12 only) - if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12' - env: - MPLBACKEND: Agg - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - pytest --cov=hypertools --cov-report=xml --cov-report=term-missing + pytest -v --tb=short --cov=hypertools --cov-report=xml --cov-report=term-missing - name: Upload coverage to Codecov (Ubuntu Python 3.12 only) if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12' diff --git a/RELEASE_CHECKLIST.md b/RELEASE_CHECKLIST.md index 49d3bfb9..964395cf 100644 --- a/RELEASE_CHECKLIST.md +++ b/RELEASE_CHECKLIST.md @@ -215,7 +215,11 @@ same artifacts you verify are the ones you publish. `git push --force origin refs/tags/v1.1.0`. A moved tag is safe ONLY because nothing has been published from the old one (PyPI still has 1.0.0, the GitHub release is a draft); once PyPI has 1.1.0 the tag is - frozen. + frozen for anything that changes the package. (On 2026-10-10, after the + PyPI upload, the tag was moved once more for a docs-only fix to the + gallery notebooks, by the maintainer's decision: the package code is + identical, so the PyPI files still match the tag's `hypertools/` tree, + but they were built from the earlier commit `cf76a931`.) - [ ] Confirm: `git ls-remote origin refs/tags/v1.1.0^{}` == the new sha. - [ ] Wait for the `v1.1.0` tag CI to go GREEN (the workflow's `tags: ['v*']` trigger runs the same jobs; `release-gate` + `docs-clean` gallery scan diff --git a/notes/session_2026-10-08_visual-signoff.md b/notes/session_2026-10-08_visual-signoff.md index aee97a8f..a257e1e6 100644 --- a/notes/session_2026-10-08_visual-signoff.md +++ b/notes/session_2026-10-08_visual-signoff.md @@ -269,3 +269,9 @@ Branch `fix/1.1-release-review`; PR #286 still OPEN and unmerged. Nothing merged playback cell after sphinx-gallery writes the notebooks (docs/_gallery_notebooks.py); check_release_notebooks.py rejects an unfloored install; tests added. What we missed: no gallery notebook was run on Colab against the published package; the checklist now says to. +- Jeremy: "can we move the tag forward so the notebook links work? it's OK for tutorials to be out of + sync with pypi, since the code itself (of the package) is unchanged". Plan: fast-forward master to + this branch, cold gallery build, rehearsal, publish + record, notebooks, gates, push master and move + the tag together, re-sync RTD stable. No new PyPI upload; dist/ untouched. +- Batched into the same commit: the ubuntu-3.12 CI job now runs the suite once (pandas-3 gate + coverage + in one pass) instead of three times; expected run length ~50 min instead of ~2 h. From 428b998a9674a8e27f52a688502c3d9ce93f9544 Mon Sep 17 00:00:00 2001 From: Jeremy Manning Date: Sat, 10 Oct 2026 12:07:24 -0400 Subject: [PATCH 3/3] release: record the pre-built gallery Built from 2765df086b3491bdff3b1ba47e3148fb6ac1883a; fetched by commit id in the Read the Docs build. Co-Authored-By: Claude Opus 5.5 --- docs/prebuilt_gallery.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/prebuilt_gallery.json b/docs/prebuilt_gallery.json index c0f1025c..8ba6821a 100644 --- a/docs/prebuilt_gallery.json +++ b/docs/prebuilt_gallery.json @@ -1,5 +1,5 @@ { - "commit": "6a194eab1a2a9210ee653481863f8874d6d3b501", - "source_commit": "391637673efb7d4450d4cf907c69c5b76c3730af", + "commit": "519b4af2dd63c3f32b120696d8afb403d8e06068", + "source_commit": "2765df086b3491bdff3b1ba47e3148fb6ac1883a", "version": "1.1.0" }