Originally created by: Pranav-Bobde
Reproduced with ainotate 0.1.8, installed via uv tool install "ainotate[all]", on Python 3.12.
The HTML guide heading and document title use the last step's title instead of the guide title. Markdown keeps the correct guide title.
Create two tiny images using a Python interpreter with Pillow installed:
mkdir -p /tmp/ainotate-repro
cd /tmp/ainotate-repro
python - <<'PY'
from PIL import Image
for name in ("first.png", "last.png"):
Image.new("RGB", (16, 16), "white").save(name)
PY
Save this as steps.json in that directory:
{
"title": "My Guide",
"intro": "Intro with **bold**.\n- Intro item",
"steps": [
{"image": "first.png", "title": "First", "text": "Step with **bold**.\n- Step item"},
{"image": "last.png", "title": "Last", "text": "Done."}
]
}
Run:
ainotate guide steps.json --formats md,html --out-dir /tmp/ainotate-repro
Expected: My Guide.html contains <h1>My Guide</h1> and <title>My Guide</title>.
Actual: it contains <h1>Last</h1> and <title>Last</title>. My Guide.md correctly starts with # My Guide.
In src/ainotate/export_html.py, _html reassigns its title parameter inside the step loop. Exact lines in 0.1.8, also present on current main:
100: def _html(title, intro, steps) -> str:
...
102: for i, s in enumerate(steps, 1):
...
105: title = _inline(s.get("title", ""))
106: if title:
107: head_html = f'<span class="t">{title}</span>'
...
115: head = f"<h1>{html.escape(title)}</h1>" if title else ""
...
119: f'<title>{html.escape(title or "Guide")}</title><style>{_CSS}</style></head>'
Suggested fix: rename the loop-local title to step_title, including its uses on lines 106–107, preserving the guide's title parameter.
Source inspection also shows the Playwright PDF path prints this same HTML (export.py:225–237), so it inherits the heading bug. This repro checks Markdown and HTML; the Pillow PDF fallback uses a separate renderer.
The same repro leaves the intro as literal **bold** and - Intro item, while the step text produces <strong> and <ul> markup. The Markdown output preserves the intro's Markdown syntax for Markdown renderers.
Line 116 only escapes the intro and replaces newlines:
116: lead = f'<p class="intro">{html.escape(intro).replace(chr(10), "<br>")}</p>' if intro else ""
Step text uses _rich() on line 108. Applying equivalent formatting to the intro, with a container suitable for paragraphs/lists, would make the outputs consistent.