Problem
CI runs notebooks via jupyter nbconvert --execute — each cell once, top-to-bottom, with no widget interaction. So bugs that only appear when a user interacts with an ipywidgets control (moves a slider), or that depend on shared global state mutated by later cells, are invisible to CI.
Concrete example (just fixed in 60_linear_algebra_2/200)
The Power Method arrow-slider callback show_iterate(step) read the global n. Cells 49/52/58 (lam, vecX, n = power_method(...)) overwrite n → 99999 for the non-converging "What if" matrices. After running those cells, moving the slider raised:
ValueError: shape mismatch ... (99999,) vs (2,) # ax.bar(range(99999), vec[2])
CI passed cleanly because:
- the
interact callback only fires once, at creation, when n was still correct;
- nbconvert never moves the slider, and never re-fires the callback after
n is clobbered;
- the
if os.getenv('CI'): show_iterate(last) guard renders one static frame — by design, to keep CI green — which also means the interactive path is never exercised.
(Fixed by capturing the dimension locally: dim = vec_array.shape[1], independent of the mutable global n.)
Options
- Convention (cheapest): widget callbacks must capture all needed state locally in their own cell, never reading mutable globals that later cells reassign. Make it a review/lint checklist item.
- Exercise callbacks in a test: for notebooks containing
interact(...), after executing all cells, call the callback across its full slider range and assert no exception. Non-trivial to wire generically.
- Static check: flag a widget callback that references a module-level name reassigned elsewhere in the same notebook. Hard to do reliably (needs data-flow analysis).
Related
Same family as #440 and the "missing Colab clone cell" gap — CI executes the repo, non-interactive path, not the interactive/Colab path.
🤖 Filed via Claude Code after fixing the 200 slider crash.
Problem
CI runs notebooks via
jupyter nbconvert --execute— each cell once, top-to-bottom, with no widget interaction. So bugs that only appear when a user interacts with anipywidgetscontrol (moves a slider), or that depend on shared global state mutated by later cells, are invisible to CI.Concrete example (just fixed in
60_linear_algebra_2/200)The Power Method arrow-slider callback
show_iterate(step)read the globaln. Cells 49/52/58 (lam, vecX, n = power_method(...)) overwriten→99999for the non-converging "What if" matrices. After running those cells, moving the slider raised:CI passed cleanly because:
interactcallback only fires once, at creation, whennwas still correct;nis clobbered;if os.getenv('CI'): show_iterate(last)guard renders one static frame — by design, to keep CI green — which also means the interactive path is never exercised.(Fixed by capturing the dimension locally:
dim = vec_array.shape[1], independent of the mutable globaln.)Options
interact(...), after executing all cells, call the callback across its full slider range and assert no exception. Non-trivial to wire generically.Related
Same family as #440 and the "missing Colab clone cell" gap — CI executes the repo, non-interactive path, not the interactive/Colab path.
🤖 Filed via Claude Code after fixing the 200 slider crash.