- st_demo: persist failure in call history, clear active_handle,
and disable Run while call active to avoid overwriting handle
- JupyterApp.shutdown: gather all shutdowns with return_exceptions=True
and re-raise as BaseExceptionGroup so one failure doesn't leak kernels
- messages: fallback known-but-malformed messages to UnknownJupyterMessage
via ValidationError, make Unknown channel permissive (str) for nuance
- transport: record reader failures as terminal state (_reader_failure)
and fail fast from execute/messages_for instead of hanging
- shell: don't reset execution_count to 0 when IPython returns None
- README: use ipython_shell imports
Skipped non-urgent/deferred items: line(), get_shell_2, test renames,
import laziness, call-ID bounding, settle window config (worth doing later).