From d03578078d0aa0d16b207bfaa7c914b4993dfa7e Mon Sep 17 00:00:00 2001 From: AE <98699388+A-EDev@users.noreply.github.com> Date: Tue, 20 Oct 2026 18:15:24 +0300 Subject: [PATCH 1/2] ci(nightly): publish per-PR nightly notes as the nightly release body --- .../2026-10-20-ci-nightly-notes.md | 2 + .github/nightly-notes/README.md | 23 +++++ .github/scripts/nightly_notes.py | 84 +++++++++++++++++++ .github/workflows/build.yml | 17 +++- 4 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 .github/nightly-notes/2026-10-20-ci-nightly-notes.md create mode 100644 .github/nightly-notes/README.md create mode 100644 .github/scripts/nightly_notes.py diff --git a/.github/nightly-notes/2026-10-20-ci-nightly-notes.md b/.github/nightly-notes/2026-10-20-ci-nightly-notes.md new file mode 100644 index 0000000000..d6f88e7e6e --- /dev/null +++ b/.github/nightly-notes/2026-10-20-ci-nightly-notes.md @@ -0,0 +1,2 @@ +## New +- Nightly updates now list what changed, grouped into new, improved and fixed (#1330) diff --git a/.github/nightly-notes/README.md b/.github/nightly-notes/README.md new file mode 100644 index 0000000000..120729ba99 --- /dev/null +++ b/.github/nightly-notes/README.md @@ -0,0 +1,23 @@ +# Nightly notes + +Every pull request with a change users can notice adds one file here. On each push to `main`, CI +merges all of them into the body of the rolling `nightly` release, which the in-app update screen +shows to nightly testers. + +- Name the file `YYYY-MM-DD-.md`: the date the PR is opened, then the branch name in + lowercase with `/` replaced by `-`. One file per PR, and never edit another PR's file, so open + PRs never conflict. +- Use only `## New`, `## Improved` and `## Fixed`, each followed by one short `- ` bullet per change. +- Write for users, in one line per bullet, with issue numbers as `(#123)` and no em-dashes. +- CI, test and refactor-only PRs add no file. + +```markdown +## Fixed +- Music no longer stays in audio offload after a long pause (#1364) +``` + +When a stable version is released, these notes are folded into `release-notes.md` and the +`app/src/main/assets/changelog/v*.txt` changelog, and every file here except this README is deleted. + +`python3 .github/scripts/nightly_notes.py --check` validates the files; the Checks job runs it on +every PR. diff --git a/.github/scripts/nightly_notes.py b/.github/scripts/nightly_notes.py new file mode 100644 index 0000000000..004f6581fc --- /dev/null +++ b/.github/scripts/nightly_notes.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 +"""Checks the nightly notes, or merges them into the nightly release body. + +Each file in .github/nightly-notes/ holds one PR's user-visible changes as `##
` headings +followed by `- ` bullets. The in-app update screen (ReleaseNotes.kt) shows the intro paragraph, then +one group per `##` section, so the merged body keeps exactly that shape. + + nightly_notes.py --check + nightly_notes.py --intro "Built from ..." > body.md +""" + +import argparse +import pathlib +import re +import sys + +SECTIONS = ("New", "Improved", "Fixed") +NOTES_DIR = pathlib.Path(__file__).resolve().parents[1] / "nightly-notes" +FILE_NAME = re.compile(r"\d{4}-\d{2}-\d{2}-[a-z0-9][a-z0-9.-]*\.md") + + +def read_note(path): + sections = {} + errors = [] + current = None + for number, raw in enumerate(path.read_text(encoding="utf-8").splitlines(), 1): + line = raw.strip() + where = f"{path.name}:{number}" + if not line: + continue + if "—" in line: + errors.append(f"{where}: no em-dashes in user-facing copy") + if line.startswith("## "): + current = line[3:].strip() + if current not in SECTIONS: + errors.append(f"{where}: section '{current}' must be one of {', '.join(SECTIONS)}") + sections.setdefault(current, []) + elif line.startswith("- "): + if current is None: + errors.append(f"{where}: bullet before any '## Section' heading") + else: + sections[current].append(line[2:].strip()) + else: + errors.append(f"{where}: expected a '## Section' heading or a '- ' bullet") + if not any(sections.values()): + errors.append(f"{path.name}: has no bullets") + return sections, errors + + +def main(): + parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("--check", action="store_true", help="only validate the notes") + parser.add_argument("--intro", default="", help="paragraph shown above the sections") + args = parser.parse_args() + + notes = sorted(path for path in NOTES_DIR.glob("*.md") if path.name != "README.md") + merged = {section: [] for section in SECTIONS} + errors = [] + # File names start with the date, so reversed order lists the newest changes first. + for path in reversed(notes): + if not FILE_NAME.fullmatch(path.name): + errors.append(f"{path.name}: name must be YYYY-MM-DD-.md in lowercase") + sections, problems = read_note(path) + errors += problems + for section, items in sections.items(): + merged.setdefault(section, []).extend(items) + + if errors: + print("\n".join(errors), file=sys.stderr) + return 1 + if args.check: + print(f"{len(notes)} nightly notes are valid.") + return 0 + + lines = [args.intro] if args.intro else [] + for section in SECTIONS: + if merged[section]: + lines += ["", f"## {section}", *(f"- {item}" for item in merged[section])] + print("\n".join(lines).strip()) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 472511c8f5..d6e89e1496 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -70,6 +70,10 @@ jobs: git status --porcelain -- app/src/main/java/org/tartarus/snowball test -z "$(git status --porcelain -- app/src/main/java/org/tartarus/snowball)" + # The nightly job publishes these as the release body the update screen shows. + - name: Check nightly notes + run: python3 .github/scripts/nightly_notes.py --check + - name: Check Kotlin formatting run: > ./gradlew spotlessCheck @@ -457,6 +461,14 @@ jobs: cancel-in-progress: false steps: + - name: Checkout nightly notes + uses: actions/checkout@v7 + with: + ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} + sparse-checkout: | + .github/nightly-notes + .github/scripts + - name: Download nightly APK uses: actions/download-artifact@v8 with: @@ -483,9 +495,12 @@ jobs: gh release delete-asset nightly "$old" --yes fi done + python3 .github/scripts/nightly_notes.py \ + --intro "Built from ${GITHUB_SHA::7} by run ${GITHUB_RUN_NUMBER}. Installs next to the stable app as Flow Nightly and updates itself from here." \ + > nightly-body.md gh release edit nightly --prerelease --latest=false \ --title "Flow nightly ${GITHUB_RUN_NUMBER}" \ - --notes "Built from ${GITHUB_SHA::7} by run ${GITHUB_RUN_NUMBER}. Installs next to the stable app as Flow Nightly and updates itself from here." + --notes-file nightly-body.md release: name: Create Release From decbde95a97e7befc56be35e19f2cc5b5c2eb2c3 Mon Sep 17 00:00:00 2001 From: AE <98699388+A-EDev@users.noreply.github.com> Date: Tue, 20 Oct 2026 18:15:24 +0300 Subject: [PATCH 2/2] docs(agents): add a nightly note to every user-visible PR --- AGENTS.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index 5a5d6b8619..e5d34a48ea 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -450,6 +450,16 @@ rows, bespoke empty/error states, bespoke badges, bespoke formatters, or a secon 3. Follow current Kotlin and Android best practices — when unsure, check official docs rather than guessing. 4. DO NOT edit the app's Room database schema without explicit instruction (schema changes require a version bump and migration, handled deliberately). 5. DO NOT bump the app version in any file — version bumps are done manually by the project owner. +6. Every PR with a change users can notice adds a nightly note: one new file + `.github/nightly-notes/YYYY-MM-DD-.md` (the date the PR is opened, the branch name in + lowercase with `/` replaced by `-`) holding `## New`, `## Improved` or `## Fixed` headings, each + with one short `- ` bullet per change, written for users, issues as `(#123)`, no em-dashes. One + file per PR, never edit another PR's file. CI, test and refactor-only PRs add none. CI publishes + every note as the nightly release body that the in-app update screen shows, and the Checks job + fails on a malformed note (`python3 .github/scripts/nightly_notes.py --check`). This is separate + from the release changelog in `app/src/main/assets/changelog/v*.txt`; at a stable release the + notes are folded into it and into `release-notes.md`, and the note files are deleted (keep the + folder's `README.md`). ## AI-only guidelines