v1.1 is the first maintenance release on the v1 plugin architecture. It is mostly **security and bug fixes**, and it is the release where the plugin ecosystem actually started shipping: every gem with pending work is now published, and releases are automated rather than manual.

Most of the user-visible change is in the gems, not in this repo.

> **`bundle update` on its own will not upgrade you.** The `Gemfile` pins every plugin to an exact version (`= 1.0.x`), and Bundler honours the pin already in _your_ `Gemfile` — so a v1.0 site runs `bundle update` and stays on the vulnerable versions, with no error. **You must edit the pins first** — see the **Upgrading** section at the bottom.

The starter's `Gemfile` now pins:

| gem                | v1.0   | v1.1       |
| ------------------ | ------ | ---------- |
| `al_folio_core`    | 1.0.10 | **1.0.12** |
| `al_folio_cv`      | 1.0.0  | **1.0.2**  |
| `al_folio_distill` | 1.0.2  | **1.0.3**  |
| `al_analytics`     | 1.0.0  | **1.0.2**  |
| `al_ext_posts`     | 1.0.1  | **1.0.3**  |
| `al_img_tools`     | 1.0.2  | **1.0.3**  |
| `al_search`        | 1.0.2  | **1.0.3**  |
| `al_math`          | 1.0.1  | **1.0.2**  |

---

## 🔒 Security

Three real vulnerabilities, all of which affected every v1.0 site.

**Swiper prototype pollution — CVSS 9.4** (`al_img_tools` 1.0.3)
CVE-2026-27212 / GHSA-hmx5-qpq5-p643 affects Swiper `>= 6.5.1, < 12.1.2`; v1.0 shipped 11.0.5. Bumped to 12.1.2 with refreshed SRI hashes for the CSS, JS and source maps. The image slider uses the `swiper-element` Web Component bundle, whose `<swiper-container>` API is unchanged across the major bump, so no template changes are needed.

**polyfill.io removed from the MathJax path** (`al_math` 1.0.2)
The polyfill.io CDN was taken over in a June 2024 supply-chain attack and had been serving malicious payloads. It was also unnecessary — MathJax 3 does not need it on any browser al-folio supports. The `<script>` tag is gone.

**Distill runtime no longer loaded from a third-party origin without integrity** (`al_folio_distill` 1.0.3)
The vendored `transforms.v2.js` carried upstream's `Polyfills` transform, which _removed_ the page's local `template.v2.js` tag and re-injected it from a hard-coded `https://distill.pub/template.v2.js` with no subresource integrity — silently discarding the hash-pinned copy the gem exists to ship, and granting arbitrary JS execution on every Distill page to whoever controls that origin. The runtime is now served from the vendored copy with `integrity` pinned to digests committed in `provenance.json`, and a build-time check reports drift instead of letting it fail SRI in visitors' browsers.

Remote loading is now explicit opt-in (`al_folio.distill.allow_remote_loader`, default **`false`**), and `sync_distill.sh` exits non-zero if a future re-sync reintroduces the remote loader.

> **Note for existing sites:** if your `_config.yml` still has `al_folio.distill.allow_remote_loader: true`, set it to `false`. Under 1.0.2 that flag was inert; under 1.0.3 leaving it `true` opts you back out of the protection. `bundle exec al-folio upgrade audit` reports this as a blocking finding.

## 🐛 Bug fixes

**Repository cards were completely blank** (`al_folio_core` 1.0.12) — two causes. The public `github-readme-stats.vercel.app` instance has been unreliable for a long time; the default is now the API-compatible, actively maintained `github-stats-extended.vercel.app` fork, so every existing query parameter keeps working. Separately, the service URL was interpolated straight from `site.external_services.*`, so a site generated from the template without that block emitted a _relative_ `/api/pin/?…` URL and every card 404'd regardless of upstream health. It now resolves through a `default:` fallback and remains overridable for self-hosting. (al-org-dev/al-folio-core#26)

**Mobile submenus rendered off-screen** (`al_folio_core` 1.0.12) — inside the collapsed navbar the dropdown inherited `position: absolute` and `right: 0` from the base Tailwind rule, anchoring it past the left edge of the viewport (measured at `left: -85.7px` on a 393px screen). Below the `sm` breakpoint the menu is now statically positioned, left-aligned, wraps long entries, and spans the full navbar width. Fixes #3663 — thanks @bibliophilecoder.

**Link previews shared without an image** (`al_folio_core` 1.0.12) — `og:image` and `twitter:image` emitted relative asset paths, which external scrapers (Discord, LinkedIn, Mastodon, Slack) cannot resolve. Relative values are now prefixed with `site.url` + `site.baseurl`, matching how `og:url` is already built; already-absolute values are left untouched so a CDN URL is not double-prefixed. Fixes #3666.

**CV entries with a bare `date` rendered no date badge** (`al_folio_cv` 1.0.2) — three of the five CV sections read only `start_date`/`startDate`, so a RenderCV entry carrying a single `date:` showed nothing. Because `al_cv_sort_by_date` _already_ sorted on that key, such an entry was silently moved into chronological position while displaying nothing to explain why. Projects gained date rendering entirely. Fixes #3339. A second bug fell out of the same pass: `{% capture %}` retains surrounding whitespace, so the emptiness test in the awards and publications sections was never true and undated entries rendered an _empty_ badge column.

**CV entries rendered in source order** (`al_folio_cv` 1.0.1) — volunteering was always appended after work history instead of being interleaved chronologically. Sorting is now handled by a new `al_cv_sort_by_date` filter that understands both RenderCV and JSONResume key names, partial dates (`2020`, `2020-06`), YAML date objects, textual `present` end dates, and undated entries. Also fixed dangling date separators: an entry with no end date renders `Present`, an entry with no dates renders no badge, and an entry with no location no longer renders a lone map-pin row.

**External posts published with empty titles** (`al_ext_posts` 1.0.3) — when a fetch degraded (unreachable page, no `<title>`, blank RSS item) the post rendered as a blank but clickable row in the blog index and produced a stream of ``Empty `slug` generated`` warnings. A readable title is now derived from the URL's last meaningful path segment and a warning naming the URL is logged. Slugs and URLs are unchanged.

**Unstyled popovers and tooltips** (`al_folio_core` 1.0.11, pinned by #3636) — the vanilla fallback used when bootstrap-compat is disabled creates `.af-popover` / `.af-tooltip` elements in `tooltips-setup.js`, but they had no positioning or visual styling at all and appeared as unpositioned floating text.

**Broken alt-text where a stat card failed** (`al_folio_core` 1.0.11, pinned by #3636) — all repository stat-card images now carry an `onerror` handler, so when the external stat-card or trophy service is unavailable the card is hidden gracefully instead of showing broken alt-text.

## ✨ Improvements

- **Cloudflare Web Analytics support** (`al_analytics` 1.0.2) — free, cookieless and unsampled. Set `analytics.cloudflare` to your beacon token. Fixes #3351.
- **Simple Analytics support** (`al_analytics` 1.0.1) — now documented, having shipped undiscoverable. Unlike every other provider it has no site ID to configure (it identifies a site by domain), so it is controlled by `enable_simple_analytics` alone.
- **Faster search-data generation** (`al_search` 1.0.3) — the full `site.pages` scan used to find the home page title is replaced by a single filter chain, with regression coverage.
- **Self-hosted star history chart** (#3684, #3685) — the README chart previously came from star-history.com, which was down. It is now generated in-repo by `bin/generate_star_history.py` (stdlib only, no dependencies), themed for light and dark, and refreshed automatically on every push to `main` plus weekly.

## 📚 Documentation

A full pass for accuracy and agent-friendliness (#3681):

- `AGENTS.md` is now the authoritative entry point for coding agents — change routing, the gem-ownership stop sign, the three silent failure modes, and the validated command set.
- `docs/ARCHITECTURE.md` explains how the starter and gems fit together; `docs/BOUNDARIES.md` is the authoritative area-to-gem ownership table.
- The showcase moved out of the README into `docs/SHOWCASE.md`.
- Factual errors corrected throughout, and each fact now lives in exactly one place with links rather than restatements.

## 🔧 Infrastructure

- **Release automation.** No gem had ever been published from CI — which is why several sat unreleased on `main` while fixes shipped nowhere. All 16 plugin repos now have a tag-driven `release.yml` that verifies the tag matches the gemspec version, refuses to overwrite an already-published version, runs the test suite against the built artifact, and will not run from a fork.
- **Dependency bumps:** `nokogiri` 1.19.3 → 1.19.4 (#3653), `concurrent-ruby` 1.3.6 → 1.3.7 (#3654), `loofah` 2.25.1 → 2.25.2 (#3676), `json` 2.19.7 → 2.19.9 (#3678).
- Dead `third_party_libraries.polyfill` config entry removed now that nothing reads it.

## ⬆️ Upgrading

Because the pins are exact, upgrading is a two-step process. Running Bundler alone changes nothing.

**1. Update the pins in your `Gemfile`** to the v1.1 column in the table above. If you have not customised the file, copying the `group :al_folio_plugins` block from this release is the quickest way.

**2. Update `_config.yml`:**

```yaml
al_folio:
  distill:
    allow_remote_loader: false # was true; leaving it true opts you out of the Distill hardening
```

You can also delete the now-unused `third_party_libraries.polyfill` entry — nothing reads it since `al_math` 1.0.2.

**3. Then install and verify:**

```bash
bundle install
bundle exec al-folio upgrade audit
```

The audit reports a **blocking** finding while `allow_remote_loader` is still `true`, which is the backstop for step 2. To confirm the security fixes actually landed, check the resolved versions rather than assuming:

```bash
bundle list | grep -E 'al_img_tools|al_math|al_folio_distill'
```

You want `al_img_tools 1.0.3` or newer — that is the one carrying the CVSS 9.4 fix. Everything in this release is otherwise backwards compatible; no template, layout or content changes are required.

---

**Full Changelog**: https://github.com/alshedivat/al-folio/compare/v1.0...v1.1
