diff --git a/pyproject.toml b/pyproject.toml
index 27fa4e0..0940b89 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "manifoldbt"
-version = "0.13.0"
+version = "0.13.1"
description = "Rust-powered backtesting engine for quantitative research"
requires-python = ">=3.9"
license = { file = "LICENSE" }
diff --git a/python/manifoldbt/plot/tearsheet.py b/python/manifoldbt/plot/tearsheet.py
index 18ec834..5128859 100644
--- a/python/manifoldbt/plot/tearsheet.py
+++ b/python/manifoldbt/plot/tearsheet.py
@@ -214,8 +214,11 @@ def tearsheet(
# ── plotly.js include ─────────────────────────────────────────
if plotlyjs == "inline":
- import plotly.io as pio
- plotly_js_tag = f""
+ # get_plotlyjs lives in plotly.offline. plotly.io has never exposed it
+ # (checked on 5.24 and 6.9), so the old plotly.io lookup raised
+ # AttributeError on every inline report rather than embedding anything.
+ from plotly.offline import get_plotlyjs
+ plotly_js_tag = f""
else:
plotly_js_tag = ''
diff --git a/python/tests/test_plot_tearsheet.py b/python/tests/test_plot_tearsheet.py
new file mode 100644
index 0000000..3e1a27c
--- /dev/null
+++ b/python/tests/test_plot_tearsheet.py
@@ -0,0 +1,91 @@
+"""Tearsheet rendering, in particular the offline (``plotlyjs="inline"``) report.
+
+The inline path was written against ``plotly.io.get_plotlyjs``, which does not
+exist and never has, so every offline report raised AttributeError. Nothing
+exercised it, so nothing caught it. These tests render both modes for real.
+"""
+import os
+
+import pytest
+
+import manifoldbt as bt
+from manifoldbt import run_with_parquet
+
+pytest.importorskip("plotly")
+
+tearsheet = pytest.importorskip("manifoldbt.plot.tearsheet").tearsheet
+
+# Roughly the size of the embedded plotly runtime; the point is to tell an
+# actually-embedded bundle from a one-line CDN script tag, not to pin a version.
+_RUNTIME_BYTES = 2_000_000
+_CDN_TAG = '