diff --git a/.github/release-notes.sh b/.github/release-notes.sh
new file mode 100755
index 0000000..f16f022
--- /dev/null
+++ b/.github/release-notes.sh
@@ -0,0 +1,136 @@
+#!/usr/bin/env bash
+#
+# Builds the GitHub release notes for a tag, the way big projects such as
+# Immich lay them out: the hand-written entry from CHANGELOG.md (a welcome,
+# the highlights), a support section, then every commit since the last
+# release, sorted by kind, and a link to the full changelog.
+#
+# .github/release-notes.sh v1.2.0 the notes
+# .github/release-notes.sh --title v1.2.0 the title (checks the entry exists)
+#
+# Commit authors come from the GitHub API through gh. Without it the list
+# goes without them.
+
+set -euo pipefail
+cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.."
+
+title=0
+if [[ ${1:-} == --title ]]; then
+ title=1
+ shift
+fi
+tag="${1:?usage: release-notes.sh [--title] vX.Y.Z}"
+
+if ! grep -qx "## $tag" CHANGELOG.md; then
+ echo "CHANGELOG.md has no entry for $tag. Add \"## $tag\" first." >&2
+ exit 1
+fi
+if (( title )); then
+ printf '%s\n' "$tag"
+ exit 0
+fi
+
+repo="${GITHUB_REPOSITORY:-$(git remote get-url origin | sed -E 's#^.*github\.com[:/]##; s#\.git$##')}"
+name="${repo#*/}"
+
+# The entry: up to the next one, without the date line (GitHub shows the
+# date), one heading level up, outside code blocks.
+entry="$(awk -v h="## $tag" '
+ $0 == h { on = 1; next }
+ on && /^## / { exit }
+ !on { next }
+ /^```/ { code = !code }
+ !code && /^_[0-9]{4}-[0-9]{2}-[0-9]{2}_$/ { next }
+ !code && /^###/ { sub(/^#/, "") }
+ { print }
+' CHANGELOG.md | sed -e '/./,$!d')"
+
+# The commits: from the last release, or from the start. A tag that does not
+# exist yet is a preview of what HEAD would become.
+if git rev-parse -q --verify "refs/tags/$tag" > /dev/null; then
+ target="$tag"
+ prev="$(git describe --tags --abbrev=0 --match 'v[0-9]*' "$tag^" 2>/dev/null || true)"
+else
+ target="$(git rev-parse HEAD)"
+ prev="$(git describe --tags --abbrev=0 --match 'v[0-9]*' HEAD 2>/dev/null || true)"
+fi
+
+declare -A login=()
+if command -v gh > /dev/null; then
+ if [[ -n $prev ]]; then
+ api=(api "repos/$repo/compare/$prev...$target" --jq '.commits[] | [.sha, (.author.login // "")] | @tsv')
+ else
+ api=(api --paginate "repos/$repo/commits?sha=$target&per_page=100" --jq '.[] | [.sha, (.author.login // "")] | @tsv')
+ fi
+ while IFS=$'\t' read -r sha who; do
+ [[ -n $who ]] && login[$sha]="$who"
+ done < <(gh "${api[@]}" 2>/dev/null || true)
+fi
+
+declare -A list=()
+count_maint=0
+while IFS=$'\t' read -r sha subject body; do
+ # Version bumps say nothing a reader needs.
+ [[ $subject =~ ^(chore:\ )?($name\ )?v?[0-9]+\.[0-9]+\.[0-9]+$ ]] && continue
+
+ case "$subject" in
+ *!:*) kind=breaking ;;
+ feat:* | feat\(*) kind=feat ;;
+ fix:* | fix\(*) kind=fix ;;
+ perf:* | perf\(*) kind=enh ;;
+ docs:* | docs\(*) kind=docs ;;
+ chore* | ci:* | ci\(* | build* | refactor* | test* | style*) kind=maint ;;
+ # Older commits without a prefix, sorted by what they say.
+ *README* | *readme* | *Readme*) kind=docs ;;
+ Add\ * | Put\ * | Introduce\ *) kind=feat ;;
+ Fix\ * | Repair\ * | Recover\ * | Stop\ *) kind=fix ;;
+ *\ *) kind=enh ;;
+ *) kind=maint ;;
+ esac
+ [[ $body == *"BREAKING CHANGE"* ]] && kind=breaking
+ [[ $subject == "Initial commit" ]] && kind=maint
+
+ line="* $subject"
+ [[ -n ${login[$sha]:-} ]] && line+=" by @${login[$sha]}"
+ line+=" in https://github.com/$repo/commit/$sha"
+ list[$kind]+="$line"$'\n'
+ [[ $kind == maint ]] && count_maint=$((count_maint + 1))
+done < <(git log --reverse --no-merges --format='%H%x09%s%x09%b%x1e' "${prev:+$prev..}$target" | tr '\n\036' ' \n' | sed 's/^ //')
+
+changes=''
+for pair in "breaking:🚨 Breaking Changes" "feat:🚀 Features" "enh:🌟 Enhancements" "fix:🐛 Bug fixes" "docs:📚 Documentation"; do
+ kind="${pair%%:*}"
+ [[ -n ${list[$kind]:-} ]] || continue
+ changes+="### ${pair#*:}"$'\n'"${list[$kind]}"
+done
+if [[ -n ${list[maint]:-} ]]; then
+ changes+=$'\n'"🧰 Maintenance ($count_maint)
"$'\n\n'"${list[maint]}"$'\n'" "$'\n'
+fi
+
+if [[ -n $prev ]]; then
+ full="**Full Changelog**: https://github.com/$repo/compare/$prev...$tag"
+else
+ full="**Full Changelog**: https://github.com/$repo/commits/$tag"
+fi
+
+# A release with highlights is a big one: it gets a heading and the support
+# section, as in Immich. A patch is a sentence or two and the list.
+if grep -q '^## Highlights$' <<< "$entry"; then
+ cat <
+
+----
+
+EOF
+else
+ printf '%s\n\n' "$entry"
+fi
+printf "## What's Changed\n%s\n\n%s\n" "$changes" "$full"
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..2f4c57d
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,73 @@
+# Changelog
+
+What each release brings, newest first. The [GitHub releases](https://github.com/LoonixTools/middleclick-autoscroll/releases) add every commit that went into it. Versions before `v1.1.0` came without notes.
+
+## v1.5.1
+
+_2026-09-15_
+
+A small patch. Autoscroll now works in Brave, which has its own name for it. AppImages are recognised too: an Electron AppImage gets autoscroll like any other app, and one that cannot autoscroll at all, like Modrinth, is left out of the list.
+
+## v1.5.0
+
+_2026-09-08_
+
+Welcome to middleclick-autoscroll `v1.5.0`! A middle click now scrolls everywhere instead of pasting text.
+
+```
+ Autoscroll ON
+
+ Applications covered 11 of 13
+ Steam ON
+ New applications ON
+ Middle-click paste off
+```
+
+### Highlights
+
+- Middle-click paste is off
+
+## v1.4.0
+
+_2026-09-06_
+
+Welcome to middleclick-autoscroll `v1.4.0`! This one is all about Steam.
+
+### Highlights
+
+- Steam stops reinstalling itself
+- Scripts that start Steam
+
+## v1.3.0
+
+_2026-09-04_
+
+Welcome to middleclick-autoscroll `v1.3.0`! Browsers look clean again, and Helium joins the list.
+
+### Highlights
+
+- No more warning bar in browsers
+- Autoscroll in Helium
+
+## v1.2.0
+
+_2026-09-01_
+
+Welcome to middleclick-autoscroll `v1.2.0`! Steam keeps its autoscroll now, however you start it.
+
+### Highlights
+
+- Steam keeps autoscroll
+- More ways to start Steam
+
+## v1.1.0
+
+_2026-08-24_
+
+Welcome to middleclick-autoscroll `v1.1.0`! It now works far beyond Arch, with packages that keep themselves up to date.
+
+### Highlights
+
+- Debian, Ubuntu, Fedora and openSUSE
+- Packages that update with your system
+- Steam fixes
diff --git a/CLAUDE.md b/CLAUDE.md
index 386a645..6a87016 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -14,3 +14,51 @@
- Subject as short as possible. Imperative, lowercase, no trailing period.
- Body only when something genuinely cannot be inferred from the diff.
- Never add `Co-Authored-By`, "Generated with" or any other AI attribution to commits or PR descriptions.
+
+## Releases
+
+Releases look like the ones of big projects such as Immich. `.github/release-notes.sh ` builds
+the notes: the entry from `CHANGELOG.md` (welcome and highlights), a support section, every commit
+since the last release sorted by its prefix (`feat`, `fix`, `docs`, ...) with author and link, and
+the full changelog link. So commit subjects end up in public: keep them clear.
+
+1. Read `git log ..HEAD` and pick the version: only fixes → patch, something new → minor,
+ something that breaks or needs the user to act → major.
+2. Bump the version and add the entry at the top of `CHANGELOG.md`, in one commit (`chore: 1.4.0`).
+3. Push, then push the tag: `git tag v1.4.0 && git push origin main v1.4.0`. The `release` workflow
+ builds the packages and creates the release. It stops before building when the entry is missing.
+4. PKGBUILDS picks up the new release for the AUR on its own.
+
+A minor or major release (has `### Highlights`, gets a heading and the support section):
+
+```markdown
+## v1.4.0
+
+_2026-09-24_
+
+Welcome to middleclick-autoscroll `v1.4.0`! One or two sentences on what this release is about.
+
+
+
+
+
+### 🚨 Breaking changes
+
+- Only if there are any: what changed, and what the user has to do.
+
+### Highlights
+
+- First highlight, a few words
+- Second highlight
+```
+
+A picture or a snippet of the menu under the welcome is optional. The highlights stay a plain list:
+no heading or text per highlight, the list of commits explains the rest.
+
+A patch release is just a sentence or two, for example: "A small patch. The menu no longer closes
+when you press Enter." The list of commits follows on its own.
+
+- Write for users: what they notice, not how the code does it. Friendly and simple.
+- Commands and settings they type go in backticks, buttons and labels in bold.
+- To change an old release: edit its entry, commit, then
+ `gh release edit --title --notes "$(.github/release-notes.sh )"`.