* feat(website): add landing page with interactive learning roadmap
The site home was the rendered README, which neither sells the guide nor
helps learners track where they are. The new landing page explains the
value vs the official docs and turns the curriculum into a checklist
roadmap (10 modules, 60 lessons) with progress saved in localStorage.
- README now renders to guide.html; landing is index.html (English only,
since translated anchors differ)
- roadmap.json is validated at build time: every lesson heading must
resolve to an anchor, so doc drift fails the build
- light/dark theme toggle shared with the docs pages
- vendor Fraunces + Manrope; font CSS cache keyed on the request URL
- pin build_website.py PEP 723 deps to requirements.txt: markdown 3.11
mis-parses `</dev/tty>` in a code span and silently truncated the
rendered hooks page
* fix(website): address landing page review findings
Optimize the lesson -> GitHub Pages conversion in build_website.py:
- BS4 single parse: thread one BeautifulSoup tree through heading-id
normalisation, TOC extraction, and link rewriting instead of
reparsing the rendered HTML 3x per page. Build drops 2.97s -> 1.77s
(~40% faster) on the ~222-page site; html.parser reparsing was the
dominant cost.
- Nav O(n^2) -> O(n): build the sidebar section grouping once
(build_nav_skeleton) and resolve per-page relative URLs in localize_nav,
removing ~49k redundant relative_link/grouping calls.
- Read each .md once: cache source text on PageInfo.content and reuse it
across collect_pages, render_pages, and copy_assets (was 2-3 reads/page).
- CI: enable uv cache and cache scripts/.vendor-cache so deploys skip cold
dep resolution and vendor (Tailwind/Mermaid/fonts) downloads.
Public string APIs (derive_page_title, render_markdown, extract_toc,
rewrite_links, normalise_heading_ids) preserved as thin wrappers over the
new soup-based cores. Full suite green (92 tests), ruff/mypy clean.
Output is semantically identical to the prior build; the only diff is raw
hand-written <img/> badges serializing as <img></img> (valid HTML5,
renders identically; nothing validates generated site HTML).
* feat(scripts): add static website generator from markdown sources (#85)
Generate an elegant, mobile-friendly static site from the existing
tutorial markdown files. The markdown remains the single source of
truth — `scripts/build_website.py` reads from the same `.md` files the
EPUB builder uses, rewrites cross-references to site URLs, and rewrites
references to non-markdown repo files (`.json`, `.sh`, `.py`) to
GitHub blob URLs so users can jump to the source on github.com.
Highlights:
- Reuses the chapter ordering convention from `build_epub.py`
- Anchor algorithm mirrors `check_cross_references.heading_to_anchor`
for parity with the validator
- Mermaid renders client-side via `mermaid.js` (no pre-render step)
- Tailwind CSS via CDN; light/dark theme toggle; sidebar nav; in-page
TOC; prev/next page navigation; mobile responsive
- 27 unit + smoke tests covering anchors, link rewriting (including
`<source srcset>` inside `<picture>`), Mermaid handling, and a full
end-to-end build
- GitHub Pages deploy workflow at `.github/workflows/pages.yml`
Closes#85
* fix(website): use relative URLs in sidebar nav and avoid INDEX.html collision
Two bugs found by local browser dogfooding:
1. **Sidebar nav broke from deep pages.** `build_navigation` emitted raw
`output_url` values (site-root-relative) which made every sidebar link
404 from any page below the root. Moved the call inside the per-page
render loop so each page gets nav links computed relative to its own
URL — `01-slash-commands/index.html` from the root, `../01-slash-commands/...`
from a depth-1 page, `../../01-slash-commands/...` from depth-2.
2. **`INDEX.md` overwrote `index.html`.** On case-insensitive filesystems
(macOS/Windows), `INDEX.html` and `index.html` are the same file, so
`INDEX.md` clobbered the rendered `README.md`. Added `_disambiguate_url`
that detects case-insensitive collisions and suffixes the colliding
page with its source stem (`INDEX-index.html`).
Added 2 tests; full suite stays at 83 passed.
* fix(scripts): skip URLs with port in localhost/127.0.0.1 skip list
`check_links.is_skipped()` did an exact-match comparison against the
host, so `http://localhost:8080` (used in scripts/README.md as a preview
example) was not skipped and CI's link check tried to fetch it, which
fails on the GitHub runner. Strip the port before comparing.
* chore(scripts): drop vestigial mypy ignore_errors for build_website
The override silenced all mypy errors for build_website, making the
"mypy: clean" claim technically vacuous. Removing it shows mypy is
actually clean — 0 issues on build_website after type annotations
were added during PR review.
* feat(website): self-host Tailwind, Mermaid, and Inter fonts
Drop all third-party CDN dependencies from rendered pages. The site
previously loaded Tailwind from cdn.tailwindcss.com (Play CDN — JIT
compile in browser, marked not-for-production), Mermaid from
cdn.jsdelivr.net, and Inter/JetBrains Mono from fonts.googleapis.com.
Replace with a vendored toolchain:
- scripts/vendor_assets.py downloads the Tailwind standalone CLI
(Go binary, no Node toolchain), Mermaid's UMD bundle, and Google
Fonts CSS + WOFF2 files. Cached under scripts/.vendor-cache/
(gitignored), refetched only when missing.
- Tailwind compiles a per-build site/assets/tailwind.css with only
the utility classes actually used by the rendered HTML.
- Mermaid and font files land in site/assets/vendor/ and load via
relative URLs.
- Tailwind config + entry CSS live in scripts/website_templates/
alongside the Jinja template.
- build_website grows a skip_vendor flag so the smoke test runs
offline.
- pre-commit mypy hook gets types-Markdown so it can resolve the
same imports as the project venv.
Verification: 86/86 pytest pass, ruff/mypy/bandit clean, full
build produces a working site with zero external requests (verified
in a headless browser — no console errors, no failed network calls,
Mermaid diagrams render).
* fix(website): use tree URLs for repo directory links (#85)
* fix(website): include additional top-level docs (#85)