diff --git a/.github/workflows/pr-quality.yml b/.github/workflows/pr-quality.yml index 4806ab5ef..aad23f9a5 100644 --- a/.github/workflows/pr-quality.yml +++ b/.github/workflows/pr-quality.yml @@ -21,6 +21,11 @@ jobs: - name: Checkout uses: actions/checkout@v4 + - name: Verify README release window + run: | + python3 -m unittest scripts.test_check_readme_release_window + python3 scripts/check_readme_release_window.py + - name: Set up Flutter uses: subosito/flutter-action@v2 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 43c5f11ff..b77976734 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,6 +28,9 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Verify README release window + run: python3 scripts/check_readme_release_window.py + - name: Set up Java uses: actions/setup-java@v4 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index 83b4d14c8..6e4c114e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # Changelog -All notable changes to this project are documented here. This project adheres to -[Semantic Versioning](https://semver.org/spec/v2.0.0.html). Release notes for -versions prior to 1.0.7 are in the **What's new** sections of the [README](README.md). +All notable changes since v1.0.7 are documented here. This project adheres to +[Semantic Versioning](https://semver.org/spec/v2.0.0.html). The [README](README.md) +shows only the three latest releases. ## [2.1.22] - 2026-10-19 diff --git a/CODE_QUALITY_CHECKLIST.md b/CODE_QUALITY_CHECKLIST.md index 15e919ed5..a4c9f8bc3 100644 --- a/CODE_QUALITY_CHECKLIST.md +++ b/CODE_QUALITY_CHECKLIST.md @@ -30,6 +30,7 @@ This checklist must be completed before every release PR. Mark each item as pass ## Release - [ ] Version bumped in `pubspec.yaml` - [ ] `CHANGELOG.md` updated, or release notes drafted if the changelog is intentionally deferred +- [ ] README **What's new** shows the three latest stable changelog releases, with older notes retained in `CHANGELOG.md` - [ ] CI/CD builds a clean APK, or the local build command and output path are recorded - [ ] Release PR links to this checklist and includes any exceptions diff --git a/README.md b/README.md index 9f21cfa7e..cfde03040 100644 --- a/README.md +++ b/README.md @@ -110,231 +110,6 @@ sanitized implementation and validation record. prompt never loads remote note images automatically (PR #143, [@igitur](https://github.com/igitur)). -## What's new in v2.1.19 - -- **Pin chats on this device** — use a chat's action menu to keep it above the - dated Workspace Chats list or classic session list. Pins persist per saved - connection, survive app restarts and chat compression, and are included in - app backups. Pinning does not alter Hermes's server-side state or another - device's order; search and chat filters still apply (PR #140). - -## What's new in v2.1.18 - -- **Composer controls stay above the keyboard** — multiline captions and - multiple attachment drafts now remain usable in portrait, landscape, narrow - screens, large text, and layouts containing gateway notices. Drafts and - status details scroll before the editor and Send button move off-screen. - -## What's new in v2.1.17 - -- **Model selection on API-only connections** — choose and apply a provider and - model over port `8642` without enabling the Desktop Dashboard on port `9119`. - The choice stays attached to the same chat and new chats retain automatic - titles. - -## What's new in v2.1.16 - -- **Correct Project labels in chat** — the sticky chat header now resolves its - authoritative server-side Project after opening, reconnecting, or binding a - new draft to its durable session, without delaying turn recovery. - -## What's new in v2.1.15 - -- **Desktop-parity notifications** — receive configurable Android notifications - for completed and failed turns, approvals, clarifications, plugin events, - credit changes, and other background activity, then return safely to the - owning chat. -- **Inline delivered files** — valid `MEDIA:` references become authenticated, - tappable file cards without turning streamed fragments or code examples into - downloads. -- **Drafts survive navigation and restarts** — unsent composer text is kept per - saved connection and chat, while live session chips refresh only when their - pane is visible. - -## What's new in v2.1.14 - -- **Navigation labels stay readable** — bottom-bar labels now fit on one line - on narrow phones at every supported text size and across every shipped - locale, with concise Russian bar labels that preserve the full pane titles. - -## What's new in v2.1.13 - -- **Select exactly the text you need** — long-press any chat message to open a - selectable-text dialog, then copy part or all of the message. The dialog is - readable in light and dark themes and localised in all nine shipped languages. - -## What's new in v2.1.12 - -- **Per-app language selection** — Android 13 and newer now list all nine - shipped Hermes locales in the system's app-language settings. -- **Accurate idle sessions** — unended chats no longer remain permanently - active when the Gateway omits a liveness flag; Projects placeholders are - ignored and recent activity gets a bounded five-minute window. - -## What's new in v2.1.11 - -- **Eight new languages** — the app is now localised in Japanese, Simplified - Chinese, Korean, Spanish, French, German, Brazilian Portuguese, and Russian. -- **Reliable locale behaviour** — unsupported locales fall back to English, - Russian counts use the correct plural forms, and Chinese and Portuguese - region variants do not receive an incompatible catalogue. - -## What's new in v2.1.10 - -- **Existing chats stay on target** — sending from a server-listed chat now - continues that exact session instead of creating a new sibling session. -- **Single creation path for new chats** — local drafts use recovery v2 without - racing a second legacy session creation, while explicit fallback remains safe. - -## What's new in v2.1.9 - -- **Interactive Gateway requests on Android** — clarification, approval, sudo, - and secret prompts now reach the phone during a running turn and recover - safely across reconnects. -- **Fast, safe large-chat restore** — large sessions load a bounded recent - transcript without blocking the composer, then reconcile authoritative - history without losing newer turns. -- **Accurate session status** — Android now uses the Gateway's explicit active - state instead of treating every unended session as still running. - -## What's new in v2.1.8 - -v2.1.8 fixes two connection-lifecycle problems diagnosed and resolved by -[@igitur](https://github.com/igitur) in PRs #111 and #113. - -- **Custom HTTPS ports work as entered** — an explicit Port field value is - honoured even when it is `8642`; leaving the field blank still infers `443` - for HTTPS and `8642` for HTTP. -- **Turns survive leaving the chat** — navigating away during a running turn no - longer closes its SSE connection and interrupts the work server-side. The - detached client is released after completion, failure, cancellation, or its - safety timeout. - -## What's new in v2.1.7 - -v2.1.7 brings the Android daily-driver workspace into line with stock Hermes -gateways through the extensive compatibility work contributed by -[@Thaeland](https://github.com/Thaeland) in PR #106. - -- **Reliable detached replies** — interrupted socket connections now resume the - stored session, keep retrying across long-running turns and app backgrounding, - and use durable message IDs so replies are recovered even when the server's - capped history window rolls over. -- **Stock Projects support** — new Projects receive safely provisioned folders, - chat moves use the stock `session.workspace.move` contract, and project labels, - archived chats, migration, search, and compatibility states follow the real - gateway wire shapes. -- **Correct session pagination** — pinned-session backfills can no longer skip, - duplicate, or prematurely end later pages. Mutable OFFSET scans no longer - delete local assignments they cannot prove are stale. -- **Safer transport and offline state** — stale runtime bindings reattach, - socket creation and authentication are single-flight, network requests are - bounded, and concurrent Project mutations cannot persist optimistic or stale - cache snapshots. -- **Additional resilience** — archived reads are profile-scoped and complete, - machine sessions stay out of human chat lists, deliberate reconnects are - explained, and Android can discover installed speech-recognition services. - -## What's new in v2.1.6 - -- **Private gateway certificates** — Android now trusts certificate authorities - that the device user explicitly installed, so private Caddy and Tailscale - gateway endpoints can connect without weakening normal certificate checks - (#108). - -## What's new in v2.1.0 - -v2.1.0 merges the community daily-driver workspace edition from -[@CarlosReyesPena](https://github.com/CarlosReyesPena) (PR #88): a new -Workspace home with Projects, Chats and Activity panes, server-backed session -search, config backup and restore, and quick-chat intents — plus a large -accompanying test suite (900+ tests). - -- **Workspace shell** — a new Home with attention-ranked "needs you" digest, - global New (project or quick) chat button, Activity operational timeline of - gateway turns, and a More pane that routes Cron, Skills, Memory, Settings - and the Hermes dashboard. -- **Projects pane** — server-owned Hermes projects via the gateway - `projects.*` RPC family: project tree overview, per-project chats, - migration preview and write path for local Spaces, chat moves between - projects, per-project search, and safe deletion. Legacy gateways degrade - gracefully to a labelled local-only compatibility mode. -- **Chats browser** — All / Recent / Unassigned / Archived filters, date - grouping, running/done status, pinned and archived session flags, and a - session→project label fallback for older APIs. -- **Session search** — three selectable modes: on-device (default), full-text - via the dashboard FTS5 endpoint with matching-excerpt results, and - AI-assisted query rewriting through Hermes (requires the corresponding - server support). Mode is remembered per connection; provider API keys never - leave the server. -- **Configuration backup & restore** — export all connections and app - preferences to a single encrypted file (PBKDF2 + AES-256-GCM), import in - merge or replace mode, with restore reachable from the empty-connection - state before any server is configured. -- **Quick chat lifecycle** — share-target and app-shortcut entry points, - share/review sheets for text and files, and a 72-hour quick-chat archive - policy that never archives blocked or running work. -- **Resilience** — gateway capability discovery (never assume the newest - server), fresh TCP per request with a 20 s timeout to fix stale keep-alive - hangs, and runtime Android 13+ notification permission request. -- **Chat UI** — sticky context header (project, model, reasoning effort, - connection state), You/Hermes role labels, long-press action sheet, real - fenced code blocks with copy and wrap/scroll toggle, and full tool output - on expanded activity cards. - -## What's new in v2.0.1 - -- **Background turn notifications** — Android notifications fire when a Desktop - Gateway turn completes while the app is backgrounded, mirroring Hermes Desktop - tray notification behaviour. Notifications are automatically cleared when you - return to the app. -- **Scroll bug resolved** — the inherited mid-history scroll-jump on session - reopen (from upstream PR #71) has been fully resolved. The - `ChatScrollCoordinator` only aligns to the end on initial load, never inside - the streaming completion handler. - -## What's new in v2.0.0 - -v2.0.0 merges the community Remote Gateway edition from -[@CristianGCiocoi](https://github.com/CristianGCiocoi). - -- **Unified Desktop Gateway JSON-RPC transport** — text, images, and files share - one WebSocket session instead of split REST+WebSocket paths. -- **Per-chat model and thinking-effort selection** without changing the profile - default. -- **Multi-attachment uploads** — up to 10 files per message, 16 MiB each. -- **Copy/Select, Read Aloud, Edit+Resend, Regenerate, Stop, Export, Search, - Rename, Branch, Delete** for conversations. -- **Native Desktop Gateway event handling** — approvals, sudo, secrets, - clarifications, reasoning, tool activity, notifications, background results, - reviews, and subagents. -- **Voice dictation** — stage voice input before explicit send. -- **Durable turn recovery** — completed gateway responses survive Android - process-kill lifecycle events. -- **Persistent accessible text size** setting. -- **ATLAS document intake metadata** for mobile uploads. -- **Secure Android credential storage** with platform key material. -- **113+ Flutter tests** plus a synthetic gateway contract suite under - `tools/fake_gateway/`. - -## What's new in v1.0.8 - -- **Reverse-proxy path prefixes** — configure separate path prefixes for the Gateway API and dashboard, e.g. `/profile/peter` before `/api` and `/v1`, and `/dashboard` before dashboard `/api` routes. -- **Proxied dashboard mode** — enable **Dashboard behind proxy** when nginx/Caddy/your host injects dashboard authentication. In this mode the app sends clean dashboard requests without trying to scrape a dashboard session token or perform password login. -- **Prefix-aware validation and chat** — API-key validation, session browsing, existing chat history, streaming chat completions, and dashboard drawer screens all use the configured prefixes. - -## What's new in v1.0.7 - -- **Password-protected dashboards** — the Memory/Cron/Skills/Settings tabs now work against a dashboard secured with basic-auth, not just an open (`--insecure`) one. The app logs in via the dashboard's `/auth/password-login` flow and reuses the session cookie (the same mechanism the desktop client uses). -- **Configurable dashboard port** — set a custom dashboard port per connection when it isn't the default `9119`. -- **Dashboard details in the connection flow** — set the dashboard port/username/password while adding a connection (expand **Custom dashboard details**) or later via **⋮ → Dashboard Login**, with validation before saving. - -## What's new in v1.0.6 - -- **Voice chat support** — tap the microphone in chat to dictate a message to Hermes, and Hermes can speak the response back. -- Spoken replies can be toggled from the chat input bar. -- Android/iOS microphone and speech-recognition permissions are included. - ## Features - **Hermes chat on Android** — browse sessions, create new chats, and send prompts to your Hermes Agent. @@ -354,19 +129,23 @@ v2.0.0 merges the community Remote Gateway edition from ## Screenshots +The Home, Chats, More, Cron Jobs and Settings screenshots use anonymised example details. + - - - + + + + - + +
Session list
Session list
Navigation drawer
Navigation drawer
Cron jobs
Cron jobs
Home dashboard with example tasks
Home
Chats browser with example conversations
Chats
More tab with workspace and organisation features
More
Cron jobs with example schedules
Cron jobs
Add cron job
Add cron job
Memory
Memory
Settings
Settings
Settings with an example model and provider
Settings
Skills
Skills
Update available dialog for Hermes Android v2.1.22
Update available
diff --git a/docs/screenshots/00-home.jpg b/docs/screenshots/00-home.jpg new file mode 100644 index 000000000..320af5e5f Binary files /dev/null and b/docs/screenshots/00-home.jpg differ diff --git a/docs/screenshots/01-session-list.jpg b/docs/screenshots/01-session-list.jpg index caaa42478..e4bbac975 100644 Binary files a/docs/screenshots/01-session-list.jpg and b/docs/screenshots/01-session-list.jpg differ diff --git a/docs/screenshots/02-more.jpg b/docs/screenshots/02-more.jpg new file mode 100644 index 000000000..1ad8dcdfe Binary files /dev/null and b/docs/screenshots/02-more.jpg differ diff --git a/docs/screenshots/02-navigation-drawer.jpg b/docs/screenshots/02-navigation-drawer.jpg deleted file mode 100644 index b46ad73e6..000000000 Binary files a/docs/screenshots/02-navigation-drawer.jpg and /dev/null differ diff --git a/docs/screenshots/03-cron-jobs.jpg b/docs/screenshots/03-cron-jobs.jpg index 602df709f..8890e7eaa 100644 Binary files a/docs/screenshots/03-cron-jobs.jpg and b/docs/screenshots/03-cron-jobs.jpg differ diff --git a/docs/screenshots/06-settings.jpg b/docs/screenshots/06-settings.jpg index f30725a00..5b18af2e0 100644 Binary files a/docs/screenshots/06-settings.jpg and b/docs/screenshots/06-settings.jpg differ diff --git a/docs/screenshots/08-update-available.png b/docs/screenshots/08-update-available.png new file mode 100644 index 000000000..7b9243291 Binary files /dev/null and b/docs/screenshots/08-update-available.png differ diff --git a/scripts/check_readme_release_window.py b/scripts/check_readme_release_window.py new file mode 100644 index 000000000..1c8860afa --- /dev/null +++ b/scripts/check_readme_release_window.py @@ -0,0 +1,51 @@ +"""Keep the README's short release window aligned with the changelog.""" + +import re +from pathlib import Path + + +HEADING = re.compile(r"^## (.+)$", re.MULTILINE) +RELEASE_HEADING = re.compile(r".*what['’]s new in v(\d+\.\d+\.\d+)$", re.IGNORECASE) +CHANGELOG_RELEASE = re.compile(r"^## \[(\d+\.\d+\.\d+)\]", re.MULTILINE) + + +def check_release_window(readme: str, changelog: str) -> list[str]: + latest = CHANGELOG_RELEASE.findall(changelog)[:3] + if len(latest) != 3: + raise ValueError("CHANGELOG.md needs at least three stable release entries") + + headings = [match.group(1) for match in HEADING.finditer(readme)] + releases = [ + (index, match.group(1)) + for index, heading in enumerate(headings) + if (match := RELEASE_HEADING.fullmatch(heading)) + ] + versions = [version for _, version in releases] + if versions != latest: + raise ValueError( + f"README.md must show exactly the three latest changelog releases " + f"{latest}; found {versions}" + ) + + first = releases[0][0] + if headings[first + 3 : first + 4] != ["Features"] or [ + index for index, _ in releases + ] != list(range(first, first + 3)): + raise ValueError("README release entries must be consecutive and end before Features") + return latest + + +def main() -> None: + root = Path(__file__).resolve().parent.parent + try: + versions = check_release_window( + (root / "README.md").read_text(encoding="utf-8"), + (root / "CHANGELOG.md").read_text(encoding="utf-8"), + ) + except ValueError as error: + raise SystemExit(str(error)) from error + print(f"README release window: {', '.join(versions)}") + + +if __name__ == "__main__": + main() diff --git a/scripts/test_check_readme_release_window.py b/scripts/test_check_readme_release_window.py new file mode 100644 index 000000000..a68c3b11c --- /dev/null +++ b/scripts/test_check_readme_release_window.py @@ -0,0 +1,49 @@ +import unittest + +from scripts.check_readme_release_window import check_release_window + + +CHANGELOG = """# Changelog +## [2.1.22] - today +## [2.1.21] - yesterday +## [2.1.20] - earlier +## [2.1.19] - older +""" +README = """## Current release +## What's new in v2.1.22 +- Change +## Optimisation Release — what's new in v2.1.21 +- Change +## What's new in v2.1.20 +- Change +## Features +""" + + +class ReleaseWindowTest(unittest.TestCase): + def test_accepts_three_latest_releases_with_decorated_heading(self): + self.assertEqual( + check_release_window(README, CHANGELOG), + ["2.1.22", "2.1.21", "2.1.20"], + ) + + def test_rejects_old_entry_left_in_readme(self): + extra = README.replace( + "## Features", "## What's new in v2.1.19\n- Change\n## Features" + ) + with self.assertRaisesRegex(ValueError, "exactly the three latest"): + check_release_window(extra, CHANGELOG) + + def test_rejects_new_release_without_rotating_readme(self): + changed = CHANGELOG.replace("## [2.1.22]", "## [2.1.23]\n## [2.1.22]") + with self.assertRaisesRegex(ValueError, "exactly the three latest"): + check_release_window(README, changed) + + def test_rejects_unrelated_heading_in_release_window(self): + changed = README.replace("## Features", "## Release archive\n## Features") + with self.assertRaisesRegex(ValueError, "consecutive and end before Features"): + check_release_window(changed, CHANGELOG) + + +if __name__ == "__main__": + unittest.main()