From 1faf5977f9f245ca4573cdc39c93d235d5772291 Mon Sep 17 00:00:00 2001 From: wrjones104 Date: Tue, 4 Aug 2026 10:17:39 -0400 Subject: [PATCH 1/5] Overhaul changelog system into a single source of truth changelog.json is now the hand-authored source of truth for both app and server release notes and versions; android/CHANGELOG.md and backend/CHANGELOG.md are generated from it (scripts/generate_changelog.py), reversing the previous flow where a fragile markdown parser regenerated changelog.json at Docker build time and silently discarded hand edits. - app.changelog: loads the two versioned arrays and derives the merged releases list + latest-version fields at load time, so they can't drift. - get_server_version()/get_android_version() now read changelog.json directly instead of build.gradle.kts, fixing a dev/prod mismatch (the Android source tree was never copied into the container). - release_notes replaces the single discord_md field with three audience-specific snippets (discord, play_store, github), read by the new release-notes skill. - generate_changelog.py --check (wired into CI) fails if the generated markdown is stale or if build.gradle.kts versionName disagrees with the newest app_releases entry. - Removed sync_changelog.py, the Docker build-time sync step, and the redundant root CHANGELOG.md/VERSION files. Co-Authored-By: Claude Sonnet 5 --- .dockerignore | 1 - .github/workflows/backend-tests.yml | 6 + .gitignore | 6 +- CHANGELOG.md | 21 -- LLM.md | 10 +- VERSION | 1 - android/CHANGELOG.md | 79 +++- backend/CHANGELOG.md | 63 ++-- backend/Dockerfile | 15 +- backend/VERSION | 1 - backend/app/changelog.py | 104 ++++++ backend/app/data/changelog.json | 491 +++++++++++++++++++++++++ backend/app/routes/whats_new_routes.py | 37 +- backend/app/utils.py | 46 +-- backend/tests/test_whats_new.py | 8 +- scripts/generate_changelog.py | 203 ++++++++++ scripts/sync_changelog.py | 179 --------- 17 files changed, 937 insertions(+), 334 deletions(-) delete mode 100644 CHANGELOG.md delete mode 100644 VERSION delete mode 100644 backend/VERSION create mode 100644 backend/app/changelog.py create mode 100644 backend/app/data/changelog.json create mode 100644 scripts/generate_changelog.py delete mode 100644 scripts/sync_changelog.py diff --git a/.dockerignore b/.dockerignore index 0ea4301..b9ec3a7 100644 --- a/.dockerignore +++ b/.dockerignore @@ -15,5 +15,4 @@ __pycache__/ .env .env.* android/ -!android/CHANGELOG.md service-account-key*.json diff --git a/.github/workflows/backend-tests.yml b/.github/workflows/backend-tests.yml index 7f5ae90..2bab147 100644 --- a/.github/workflows/backend-tests.yml +++ b/.github/workflows/backend-tests.yml @@ -8,6 +8,9 @@ on: - 'backend/**' - 'alembic/**' - 'requirements.txt' + - 'scripts/generate_changelog.py' + - 'android/app/build.gradle.kts' + - 'android/CHANGELOG.md' - '.github/workflows/backend-tests.yml' jobs: @@ -30,6 +33,9 @@ jobs: pip install -r requirements.txt pip install -r backend/requirements.txt + - name: Verify changelog is in sync + run: python scripts/generate_changelog.py --check + - name: Run backend unit tests env: # Root (for `backend.app...` imports) plus `backend` (for `app...` imports), diff --git a/.gitignore b/.gitignore index 1ef3903..7ee168c 100644 --- a/.gitignore +++ b/.gitignore @@ -61,8 +61,10 @@ Thumbs.db *.apk *.aab -# --- GENERATED FILES --- -backend/app/data/changelog.json +# backend/app/data/changelog.json is the hand-authored single source of truth +# for release notes and versions — it is tracked, not generated. The derived +# android/CHANGELOG.md and backend/CHANGELOG.md are produced from it by +# scripts/generate_changelog.py. # --- BACKEND TESTS --- # Track the project's backend test suite even if a contributor's global diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 268bf4b..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,21 +0,0 @@ -# Archipelago Alerts - Project Changelog - -Archipelago Alerts uses decoupled versioning for the Android application and the Backend server: - -- 📱 **Android App Changelog**: See [android/CHANGELOG.md](android/CHANGELOG.md) for Android UI updates, feature additions, and app releases. -- ⚙️ **Backend Server Changelog**: See [backend/CHANGELOG.md](backend/CHANGELOG.md) for API enhancements, database migrations, and server performance fixes. - ---- - -## Recent Release Summary - -### Android App (`v1.6.19`) - 2026-07-31 -- **Instant Slot Detail Navigation**: Shared `UserViewModel` across navigation routes for immediate transition into slot details and player alias rendering. -- **On-Demand Autocomplete Loading**: Deferred item/location autocomplete fetching until user interacts with dropdowns to eliminate initial screen load lag. -- **Preferences UI Cleanup**: Streamlined notification preference screens. - -### Backend Server (`v1.6.19`) - 2026-07-31 -- **Poller CPU & Resource Throttling**: Throttled concurrent room processing cycles to smooth CPU spikes. -- **Cycle Jitter & Staggering**: Added random jitter to poller sleep intervals to prevent wave synchronization. -- **SQLAlchemy Pool Tuning**: Optimized PostgreSQL connection pool size and recycling for high concurrency. -- **Datapackage Cache Lock**: Prevented redundant parallel datapackage fetches during autocomplete queries. diff --git a/LLM.md b/LLM.md index 695f1ab..9cb63af 100644 --- a/LLM.md +++ b/LLM.md @@ -12,12 +12,10 @@ The system consists of two main parts: ## Directory Structure -* `CHANGELOG.md`: Root project changelog overview pointing to component changelogs. -* `android/CHANGELOG.md`: Dedicated Android application changelog following Keep a Changelog standard. -* `backend/CHANGELOG.md`: Dedicated Backend server & API changelog following Keep a Changelog standard. -* `backend/VERSION`: Backend server version string file (e.g. `1.6.19`). +* `backend/app/data/changelog.json`: **Single source of truth** for all release notes and versions (hand-edited). Two newest-first arrays: `app_releases` (Android) and `server_releases` (Backend). Everything else — the two `CHANGELOG.md` files, the landing-page version badges, and `/api/whats_new` — is derived from it. +* `android/CHANGELOG.md` / `backend/CHANGELOG.md`: **Generated** (do not hand-edit) from `changelog.json` by `scripts/generate_changelog.py`. * `architecture.md`: **[Detailed Architecture Document](architecture.md)** — Explains system design, Mermaid diagrams, Redis event queues, service engines, PostgreSQL composite indexes, and Docker container topology. -* `scripts/`: Contains developer helper scripts including `sync_changelog.py`. +* `scripts/`: Contains developer helper scripts including `generate_changelog.py`. * `backend/`: Contains the Python backend code. * `app/`: Main application package. * `routes/`: Domain-driven REST API blueprint modules (`auth_routes.py`, `user_routes.py`, `rooms_routes.py`, `slots_routes.py`, `thresholds_routes.py`, `history_routes.py`, `game_routes.py`, `whats_new_routes.py`). @@ -99,7 +97,7 @@ The system consists of two main parts: * **Polling:** The poller uses a "Supervisor" pattern to manage tasks. It has self-healing logic for "Pending" rooms that turn into real rooms. * **Privacy:** We strictly avoid storing sensitive Discord info (email/pass). We only store ID, username, and avatar hash. * **Cheese Tracker Claim Checking:** Unauthenticated claims on Cheese Tracker leave `claimed_by_ct_user_id` as `None` but populate `discord_username` (which shows as `effective_discord_username` on GET requests). Checking for claim conflicts requires checking both for authenticated ID mismatches and unauthenticated Discord username mismatches. -* **Changelog Formatting & Sync:** Author Android release notes in `android/CHANGELOG.md` and server release notes in `backend/CHANGELOG.md`. Run `python scripts/sync_changelog.py` to automatically compile both into `backend/app/data/changelog.json` for the web landing page and `/api/whats_new` endpoints. +* **Changelog & Versioning (single source of truth):** All release notes and versions live in `backend/app/data/changelog.json` — a hand-edited file with two newest-first arrays, `app_releases` (Android) and `server_releases` (Backend), which are versioned independently. To cut a release: (1) prepend an entry to the relevant array; (2) for an Android release, bump `versionName`/`versionCode` in `android/app/build.gradle.kts` to match the new `app_releases` version; (3) run `python scripts/generate_changelog.py` to regenerate `android/CHANGELOG.md` and `backend/CHANGELOG.md`; (4) commit. The landing-page version badges, `/api/whats_new`, and `get_server_version()`/`get_android_version()` all read `changelog.json` directly. `scripts/generate_changelog.py --check` (run in CI) fails if the markdown is stale or if the gradle `versionName` disagrees with the newest `app_releases` entry. Do **not** hand-edit the `CHANGELOG.md` files — they are generated. * **APK Distribution:** APK files are not hosted directly on the web app/backend. APK downloads are provided exclusively via **GitHub Releases** (alongside Google Play Store). ## LLM Maintenance Directive diff --git a/VERSION b/VERSION deleted file mode 100644 index 49e1fe3..0000000 --- a/VERSION +++ /dev/null @@ -1 +0,0 @@ -1.6.21 diff --git a/android/CHANGELOG.md b/android/CHANGELOG.md index cc0656a..46540ac 100644 --- a/android/CHANGELOG.md +++ b/android/CHANGELOG.md @@ -1,50 +1,87 @@ + + # Android App Changelog -All notable changes to the **Archipelago Alerts Android Application** will be documented in this file. +All notable changes to the **Android App** are documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +> This file is generated from `backend/app/data/changelog.json`. + +## [1.6.22] - 2026-08-03 + +_Cheese Tracker Notes, Statuses & Ping Preferences_ -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +> **Discord Copy-Paste:** +> ```markdown +> **Archipelago Alerts v1.6.22 — Cheese Tracker Notes & Statuses** +> +> • View & edit Cheese Tracker notes and status (Unknown / Unblocked / BK / Soft BK / Go Mode) per slot +> • "Still BK" button to refresh your Last Checked time +> • Per-slot ping preference + a default ping for newly claimed slots +> • Default ping no longer stuck on "Never" when you claim a slot +> ``` + +### Added +- **Cheese Tracker Notes & Status**: View and edit your Cheese Tracker notes, progression status (Unknown, Unblocked, BK, Soft BK, Go Mode) and completion status right from a slot's detail screen. +- **"Still BK" Button**: Keep your BK/Soft BK status while refreshing your Last Checked time, matching the popular Cheese Tracker web feature. +- **Ping Preferences**: Edit per-slot ping preference on the slot detail screen and choose a default ping preference for newly claimed slots in the Cheese Tracker integration card. + +### Changed +- **Forfeit Safeguard**: Marking a slot as Forfeit now shows a confirmation, since Forfeit is permanent on Cheese Tracker and cannot be reversed. +- **Conflict Handling**: Edits that collide with concurrent changes on Cheese Tracker now surface a clear "please refresh" message instead of silently overwriting. + +### Fixed +- **Ping Default No Longer Stuck on "Never"**: Claiming a slot now applies your chosen default ping preference instead of always defaulting to "Never". + +--- ## [1.6.21] - 2026-08-03 -> **Discord Copy-Paste Format:** +_Real-Time History Sync Progress & System Improvements_ + +> **Discord Copy-Paste:** > ```markdown > **Archipelago Alerts Android App v1.6.21 Released!** -> +> > **New Features & Enhancements** > • **Real-Time History Progress**: Track history sync status live with a dynamic percentage bar (`X% / 100%`) and clear progress indicators. > • **Background History Syncing**: History syncing now continues seamlessly via WorkManager and ApplicationScope when screen is locked or app is minimized. > • **Instant Ignore & Whitelist Updates**: Mute rules and whitelists update instantly when returning to the history screen without needing an app restart. -> +> > Update now on Google Play or download the latest APK from GitHub Releases! > ``` ### Added -- **Real-Time History Sync Progress**: Added percentage calculation and `LinearProgressIndicator` banner showing exact item counts (`Syncing history... 45% (1,200 / 2,668 items)`). -- **WorkManager & ApplicationScope Execution**: Delegated sync execution to `HistorySyncManager` and Android `WorkManager` so sync jobs complete cleanly even when phone screen locks or app is backgrounded. +- **Real-Time History Sync Progress**: Added percentage calculation and LinearProgressIndicator banner showing exact item counts (Syncing history... 45% (1,200 / 2,668 items)). +- **WorkManager & ApplicationScope Execution**: Delegated sync execution to HistorySyncManager and Android WorkManager so sync jobs complete cleanly even when phone screen locks or app is backgrounded. ### Changed - **Pure Delta Synchronization**: Removed full-feed re-downloads on pull-to-refresh; sync relies strictly on slot watermarks for fast ~100ms updates. -- **Shared Ignore/Whitelist State**: Shared `UserViewModel` across `IgnoreListScreen` and `WhitelistScreen` navigation routes to update rules instantly on history screen re-entry. +- **Shared Ignore/Whitelist State**: Shared UserViewModel across IgnoreListScreen and WhitelistScreen navigation routes to update rules instantly on history screen re-entry. --- ## [1.6.19] - 2026-07-31 -> **Discord Copy-Paste Format:** +_Instant Slot Detail Navigation_ + +> **Discord Copy-Paste:** > ```markdown > **Archipelago Alerts Android App v1.6.19 Released!** -> +> > **Improvements & Fixes** > • **Instant Slot Detail Navigation**: Zero-latency screen transitions when opening slot details with dynamic player alias support. > • **On-Demand Autocomplete**: Lazy loading for item and location autocomplete options to accelerate screen loads. > • **Preferences UI Cleanup**: Streamlined notification preference screens. -> +> > Update now on Google Play or download the latest APK from GitHub Releases! > ``` ### Changed -- **Instant Slot Detail Navigation**: Shared `UserViewModel` across navigation routes for immediate transition into slot details and player alias rendering. +- **Instant Slot Detail Navigation**: Shared UserViewModel across navigation routes for immediate transition into slot details and player alias rendering. - **On-Demand Autocomplete Loading**: Deferred item/location autocomplete fetching until user interacts with dropdowns to eliminate initial screen load lag. - **Preferences UI Cleanup**: Removed duplicate help section from notification preferences screen. @@ -52,35 +89,39 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.6.18] - 2026-07-30 -> **Discord Copy-Paste Format:** +_Push Notification Whitelist & System Improvements_ + +> **Discord Copy-Paste:** > ```markdown > **Archipelago Alerts Android App v1.6.18 Released!** -> +> > **New Features** > • **Push Notification Whitelist**: Want notifications for specific items (e.g. Progressive Swords, Bombs) even if filler/category mutes are enabled? You can now whitelist individual items or item groups per-game or globally! -> +> > **Improvements & Fixes** > • **Instant History Sync**: Refactored item history synchronization using cursor watermarks for faster load times and zero missing items. > • **Item Index Tracking**: Received item ordering now tracks Archipelago's native item index for 100% item fidelity. > • **Cheese Tracker Sync**: Improved slot claim validation and conflict resolution. -> +> > Update now on Google Play or download the latest APK from GitHub Releases! > ``` ### Added -- **Push Notification Whitelist**: Added `WhitelistScreen` UI allowing users to whitelist specific items or item groups to always receive notifications regardless of mute settings. +- **Push Notification Whitelist**: Added WhitelistScreen UI allowing users to whitelist specific items or item groups to always receive notifications regardless of mute settings. - **What's New Dialog**: Interactive bottom sheet displaying release highlights upon app update. ### Changed - **Cursor-Based History Sync**: Replaced timestamp-based history watermarks with integer cursors for faster sync and robust retry handling. ### Fixed -- **History Job Cancellation**: In-flight refresh coroutines in `HistoryViewModel` are properly cancelled on repeated pull-to-refresh. +- **History Job Cancellation**: In-flight refresh coroutines in HistoryViewModel are properly cancelled on repeated pull-to-refresh. - **Database Migration 20->21**: Automatically cleans up legacy timestamp watermarks upon Android app upgrade. --- ## [1.6.14] - 2026-06-24 +_App Release v1.6.14_ + ### Fixed - **Cheese Tracker Slot Claim**: Fixed slot claim UI state syncing for unauthenticated slots. diff --git a/backend/CHANGELOG.md b/backend/CHANGELOG.md index 2a693d8..9ddc707 100644 --- a/backend/CHANGELOG.md +++ b/backend/CHANGELOG.md @@ -1,16 +1,23 @@ + + # Backend Server Changelog -All notable changes to the **Archipelago Alerts Backend Server & API** will be documented in this file. +All notable changes to the **Backend Server** are documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +> This file is generated from `backend/app/data/changelog.json`. ## [1.6.22] - 2026-08-03 -> **Discord Copy-Paste Format:** +_Cheese Tracker Notes & Statuses API_ + +> **Discord Copy-Paste:** > ```markdown > **Archipelago Alerts Backend v1.6.22 Released!** -> +> > **New: Cheese Tracker Notes & Statuses** > • **Per-Slot State API**: `GET /api/user/tracked_slots` now includes a `cheese` object per slot (notes, progression/completion status, ping, last checked, ownership). > • **Slot Editing**: New `PUT /rooms//slots//cheese` to edit notes/status/ping and refresh "Last Checked" ("Still BK"), with ownership checks and optimistic-conflict handling. @@ -18,41 +25,45 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 > ``` ### Added -- **Cheese Slot State (read)**: `get_user_tracked_slots` in `slots_routes.py` parses the room's cached Cheese Tracker data and attaches a per-slot `cheese` object (`game_id`, `notes`, `progression_status`, `completion_status`, `discord_ping`, `last_checked`, `is_mine`, `global_ping_policy`) for Cheese-connected users. -- **Cheese Slot State (write)**: New synchronous `PUT /rooms//slots//cheese` endpoint. Validates enum values, re-fetches the tracker, enforces ownership, applies partial updates, stamps `last_checked` for BK/Soft BK and "Still BK", sends `x-if-owner-is` as a conflict guard, and splices the authoritative response back into the room cache. -- **`User.cheese_default_ping`**: New nullable column (Alembic `a1c7e9f4b2d0`) exposed on the user profile and settable via `PUT /users/me/preferences`. +- **Cheese Slot State (read)**: get_user_tracked_slots parses the room's cached Cheese Tracker data and attaches a per-slot cheese object (game_id, notes, progression_status, completion_status, discord_ping, last_checked, is_mine, global_ping_policy) for Cheese-connected users. +- **Cheese Slot State (write)**: New synchronous PUT /rooms//slots//cheese endpoint. Validates enum values, re-fetches the tracker, enforces ownership, applies partial updates, stamps last_checked for BK/Soft BK and Still BK, sends x-if-owner-is as a conflict guard, and splices the authoritative response back into the room cache. +- **User.cheese_default_ping**: New nullable column (Alembic a1c7e9f4b2d0) exposed on the user profile and settable via PUT /users/me/preferences. ### Changed -- **Claim-Time Ping Default**: `send_state` in `api_cheese.py` now applies the user's `cheese_default_ping` when claiming a slot, and aligns unclaim behavior with Cheese Tracker's web UI (availability → `open`, ping → `never`). +- **Claim-Time Ping Default**: send_state in api_cheese.py now applies the user's cheese_default_ping when claiming a slot, and aligns unclaim behavior with Cheese Tracker's web UI (availability to open, ping to never). ### Fixed -- **Ping Preference Stuck on "Never"**: Newly claimed slots now honor the user's chosen default ping preference instead of always defaulting to "Never". +- **Ping Preference Stuck on Never**: Newly claimed slots now honor the user's chosen default ping preference instead of always defaulting to Never. --- ## [1.6.21] - 2026-08-03 -> **Discord Copy-Paste Format:** +_Tracked Slot Item Count Aggregation_ + +> **Discord Copy-Paste:** > ```markdown > **Archipelago Alerts Backend v1.6.21 Released!** -> +> > **Improvements & Fixes** > • **Tracked Slot Item Count Payload**: Surfaced total item counts per slot in `GET /api/user/tracked_slots` to drive client-side progress calculation. > • **Landing Page Version Syncing**: Fixed landing page version badges to resolve from `changelog.json` in production containers. > ``` ### Changed -- **Tracked Slot Item Count Aggregation**: Updated `get_user_tracked_slots` query in `slots_routes.py` to aggregate `item_count` per slot in the JSON response payload. -- **Website Version Display Alignment**: Updated `get_android_version()` in `utils.py` to check `changelog.json` so the landing page version badges stay aligned with release notes across all environments. +- **Tracked Slot Item Count Aggregation**: Updated get_user_tracked_slots query in slots_routes.py to aggregate item_count per slot in the JSON response payload. +- **Website Version Display Alignment**: Updated get_android_version() in utils.py to check changelog.json so the landing page version badges stay aligned with release notes across all environments. --- ## [1.6.19] - 2026-07-31 -> **Discord Copy-Paste Format:** +_Poller CPU & Resource Throttling_ + +> **Discord Copy-Paste:** > ```markdown > **Archipelago Alerts Backend v1.6.19 Released!** -> +> > **Improvements & Fixes** > • **Poller CPU & Resource Throttling**: Throttled concurrent room processing cycles to smooth CPU spikes. > • **Cycle Jitter & Staggering**: Added random jitter to poller sleep intervals to prevent wave synchronization. @@ -61,30 +72,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 > ``` ### Changed -- **Poller CPU & Resource Throttling**: Introduced `db_process_semaphore` (limit=3) to throttle concurrent synchronous database processing during room poll cycles, smoothing CPU usage and eliminating high-load CPU spikes. +- **Poller CPU & Resource Throttling**: Introduced db_process_semaphore (limit=3) to throttle concurrent synchronous database processing during room poll cycles, smoothing CPU usage and eliminating high-load CPU spikes. - **Cycle Jitter & Staggering**: Added per-cycle ±30s random jitter to the 5-minute poller sleep interval and expanded initial room stagger (1–60s) to prevent room polling tasks from re-synchronizing into waves over time. -- **SQLAlchemy Connection Pool Tuning**: Configured pool settings (`pool_size=10`, `max_overflow=5`, `pool_recycle=1800`, `pool_pre_ping=True`) for PostgreSQL in production to avoid connection pool exhaustion under load. -- **Docker Compose CPU & Memory Limits**: Defined resource limits and reservations for `api` and `poller` containers to guarantee API CPU availability (0.4 vCPU reserved for API, poller capped at 1.0 vCPU) on 2 vCPU VMs. -- **Per-Game Datapackage Cache Lock**: Added an in-memory per-game asyncio lock in `game_routes.py` to prevent concurrent autocomplete queries from redundantly fetching game datapackages. +- **SQLAlchemy Connection Pool Tuning**: Configured pool settings (pool_size=10, max_overflow=5, pool_recycle=1800, pool_pre_ping=True) for PostgreSQL in production to avoid connection pool exhaustion under load. +- **Docker Compose CPU & Memory Limits**: Defined resource limits and reservations for api and poller containers to guarantee API CPU availability (0.4 vCPU reserved for API, poller capped at 1.0 vCPU) on 2 vCPU VMs. +- **Per-Game Datapackage Cache Lock**: Added an in-memory per-game asyncio lock in game_routes.py to prevent concurrent autocomplete queries from redundantly fetching game datapackages. ### Fixed -- **Database Healthcheck Environment Escaping**: Escaped PostgreSQL env vars (`$$POSTGRES_USER` and `$$POSTGRES_DB`) in `docker-compose.yml` healthcheck so credentials resolve from the container's environment dynamically across dev, UAT, and prod. +- **Database Healthcheck Environment Escaping**: Escaped PostgreSQL env vars ($$POSTGRES_USER and $$POSTGRES_DB) in docker-compose.yml healthcheck so credentials resolve from the container's environment dynamically across dev, UAT, and prod. --- ## [1.6.18] - 2026-07-30 +_GET /api/whats_new Endpoint & System Improvements_ + ### Added -- **`GET /api/whats_new` Endpoint**: Backend API to dynamically fetch release notes and patch highlights with target filtering (`app`, `server`, `all`). -- **Item & Group Whitelist Schema**: Introduced `UserWhitelistItem` backend model and database migrations. +- **GET /api/whats_new Endpoint**: Backend API to dynamically fetch release notes and patch highlights with target filtering (app, server, all). +- **Item & Group Whitelist Schema**: Introduced UserWhitelistItem backend model and database migrations. ### Changed -- **Native `item_index` Preservation**: Backend poller now logs and orders received items using Archipelago's native `item_index` sequence. +- **Native item_index Preservation**: Backend poller now logs and orders received items using Archipelago's native item_index sequence. - **Database Performance**: Added composite performance indexes for history queries and room subscription polling. --- ## [1.6.14] - 2026-06-24 +_Server Release v1.6.14_ + ### Fixed - **Milestone Groups Optimizations**: Improved the backend process that supplies items and item_groups for the Milestone Group builder. diff --git a/backend/Dockerfile b/backend/Dockerfile index 857f954..6b2c7d0 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -12,21 +12,14 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ COPY backend/requirements.txt . RUN pip install --no-cache-dir -r requirements.txt -# Copy backend application source +# Copy backend application source. This includes app/data/changelog.json, the +# hand-authored single source of truth for release notes and versions served by +# /api/whats_new and the landing page (no build-time generation step). COPY backend/ /app/ -# Copy Alembic database migrations, configuration, and VERSION files +# Copy Alembic database migrations and configuration COPY alembic/ /app/alembic/ COPY alembic.ini /app/alembic.ini -COPY VERSION /app/VERSION -COPY backend/VERSION /app/backend/VERSION - -# Generate changelog.json from CHANGELOG files at build time -COPY CHANGELOG.md /app/CHANGELOG.md -COPY android/CHANGELOG.md /app/android/CHANGELOG.md -COPY backend/CHANGELOG.md /app/backend/CHANGELOG.md -COPY scripts/sync_changelog.py /app/scripts/sync_changelog.py -RUN python /app/scripts/sync_changelog.py && rm -rf /app/CHANGELOG.md /app/android/CHANGELOG.md /app/backend/CHANGELOG.md /app/scripts/sync_changelog.py # Run as non-root user for security RUN adduser --disabled-password --gecos '' appuser && chown -R appuser:appuser /app diff --git a/backend/VERSION b/backend/VERSION deleted file mode 100644 index 49e1fe3..0000000 --- a/backend/VERSION +++ /dev/null @@ -1 +0,0 @@ -1.6.21 diff --git a/backend/app/changelog.py b/backend/app/changelog.py new file mode 100644 index 0000000..8304ec8 --- /dev/null +++ b/backend/app/changelog.py @@ -0,0 +1,104 @@ +""" +changelog.py +------------ +Single source of truth loader for release notes. + +`backend/app/data/changelog.json` is authored by hand (see +`scripts/generate_changelog.py` for the derived CHANGELOG.md files). It contains +only two arrays, each ordered newest-first: + + { + "app_releases": [ , ... ], # Android application + "server_releases": [ , ... ] # Backend / API + } + +Everything else consumed by the landing page and the in-app "What's New" dialog +(the merged `releases` list and the `*_latest_version` fields) is *derived* here +at load time, so those values can never drift from the authored arrays. + +A object has the shape: + + { + "version": "1.6.22", + "component": "app" | "server", + "component_label": "App" | "Server", + "release_date": "2026-08-03", + "title": "...", + "highlights": [ {"title": "...", "description": "..."}, ... ], + "categories": {"features": [...], "improvements": [...], "fixes": [...]}, + "release_notes": { + "discord": "...", # casual, Discord-formatted announcement + "play_store": "...", # plain text, <=500 chars, Play Console "What's new" + "github": "..." # markdown, GitHub Release description + } + } + +The `.claude/skills/release-notes/SKILL.md` skill drafts new entries (highlights, +categories, and all three release_notes variants) collaboratively in chat. +""" + +import os +import json +import logging + +DATA_FILE_PATH = os.path.join(os.path.dirname(__file__), 'data', 'changelog.json') + +FALLBACK_VERSION = "1.0.0" + + +def load_changelog(): + """Loads the authored changelog arrays. Returns {app_releases, server_releases}.""" + if not os.path.exists(DATA_FILE_PATH): + logging.warning(f"[CHANGELOG] Source file not found at {DATA_FILE_PATH}") + return {"app_releases": [], "server_releases": []} + + try: + with open(DATA_FILE_PATH, 'r', encoding='utf-8') as f: + data = json.load(f) + except Exception as e: + logging.error(f"[CHANGELOG] Failed to read changelog source: {e}") + return {"app_releases": [], "server_releases": []} + + return { + "app_releases": data.get("app_releases", []), + "server_releases": data.get("server_releases", []), + } + + +def latest_version(component, data=None): + """Newest version string for 'app' or 'server' (arrays are newest-first).""" + data = data or load_changelog() + key = "app_releases" if component == "app" else "server_releases" + releases = data.get(key, []) + if releases and releases[0].get("version"): + return releases[0]["version"] + return None + + +def enrich(data=None): + """ + Expands the authored arrays into the full payload the API serves: + the two component arrays, a merged `releases` list (newest-first), and the + `latest_version` / `app_latest_version` / `server_latest_version` fields. + """ + data = data or load_changelog() + app_releases = data.get("app_releases", []) + server_releases = data.get("server_releases", []) + + app_latest = app_releases[0]["version"] if app_releases else FALLBACK_VERSION + server_latest = server_releases[0]["version"] if server_releases else FALLBACK_VERSION + + merged = app_releases + server_releases + merged.sort( + key=lambda r: (r.get("release_date", ""), r.get("version", "")), + reverse=True, + ) + + return { + "latest_version": server_latest, + "app_latest_version": app_latest, + "server_latest_version": server_latest, + "app_releases": app_releases, + "server_releases": server_releases, + "releases": merged, + } diff --git a/backend/app/data/changelog.json b/backend/app/data/changelog.json new file mode 100644 index 0000000..e246431 --- /dev/null +++ b/backend/app/data/changelog.json @@ -0,0 +1,491 @@ +{ + "app_releases": [ + { + "version": "1.6.22", + "component": "app", + "component_label": "App", + "release_date": "2026-08-03", + "title": "Cheese Tracker Notes, Statuses & Ping Preferences", + "highlights": [ + { + "title": "Cheese Tracker Notes & Status", + "description": "View and edit your Cheese Tracker notes, progression status (Unknown, Unblocked, BK, Soft BK, Go Mode) and completion status right from a slot's detail screen." + }, + { + "title": "\"Still BK\" Button", + "description": "Keep your BK/Soft BK status while refreshing your Last Checked time, matching the popular Cheese Tracker web feature." + }, + { + "title": "Per-Slot & Default Ping Preference", + "description": "Set your ping preference (Liberally, Sparingly, Hints, See Notes, Never) on individual slots, plus a default applied to newly claimed slots from the Profile screen." + } + ], + "categories": { + "features": [ + { + "title": "Cheese Tracker Notes & Status", + "description": "View and edit your Cheese Tracker notes, progression status (Unknown, Unblocked, BK, Soft BK, Go Mode) and completion status right from a slot's detail screen." + }, + { + "title": "\"Still BK\" Button", + "description": "Keep your BK/Soft BK status while refreshing your Last Checked time, matching the popular Cheese Tracker web feature." + }, + { + "title": "Ping Preferences", + "description": "Edit per-slot ping preference on the slot detail screen and choose a default ping preference for newly claimed slots in the Cheese Tracker integration card." + } + ], + "improvements": [ + { + "title": "Forfeit Safeguard", + "description": "Marking a slot as Forfeit now shows a confirmation, since Forfeit is permanent on Cheese Tracker and cannot be reversed." + }, + { + "title": "Conflict Handling", + "description": "Edits that collide with concurrent changes on Cheese Tracker now surface a clear \"please refresh\" message instead of silently overwriting." + } + ], + "fixes": [ + { + "title": "Ping Default No Longer Stuck on \"Never\"", + "description": "Claiming a slot now applies your chosen default ping preference instead of always defaulting to \"Never\"." + } + ] + }, + "release_notes": { + "discord": "**Archipelago Alerts v1.6.22 \u2014 Cheese Tracker Notes & Statuses**\n\n\u2022 View & edit Cheese Tracker notes and status (Unknown / Unblocked / BK / Soft BK / Go Mode) per slot\n\u2022 \"Still BK\" button to refresh your Last Checked time\n\u2022 Per-slot ping preference + a default ping for newly claimed slots\n\u2022 Default ping no longer stuck on \"Never\" when you claim a slot", + "play_store": "", + "github": "" + } + }, + { + "version": "1.6.21", + "component": "app", + "component_label": "App", + "release_date": "2026-08-03", + "title": "Real-Time History Sync Progress & System Improvements", + "highlights": [ + { + "title": "Real-Time History Sync Progress", + "description": "Added percentage calculation and LinearProgressIndicator banner showing exact item counts (Syncing history... 45% (1,200 / 2,668 items))." + }, + { + "title": "WorkManager & ApplicationScope Execution", + "description": "Delegated sync execution to HistorySyncManager and Android WorkManager so sync jobs complete cleanly even when phone screen locks or app is backgrounded." + }, + { + "title": "Pure Delta Synchronization", + "description": "Removed full-feed re-downloads on pull-to-refresh; sync relies strictly on slot watermarks for fast ~100ms updates." + }, + { + "title": "Shared Ignore/Whitelist State", + "description": "Shared UserViewModel across IgnoreListScreen and WhitelistScreen navigation routes to update rules instantly on history screen re-entry." + } + ], + "categories": { + "features": [ + { + "title": "Real-Time History Sync Progress", + "description": "Added percentage calculation and LinearProgressIndicator banner showing exact item counts (Syncing history... 45% (1,200 / 2,668 items))." + }, + { + "title": "WorkManager & ApplicationScope Execution", + "description": "Delegated sync execution to HistorySyncManager and Android WorkManager so sync jobs complete cleanly even when phone screen locks or app is backgrounded." + } + ], + "improvements": [ + { + "title": "Pure Delta Synchronization", + "description": "Removed full-feed re-downloads on pull-to-refresh; sync relies strictly on slot watermarks for fast ~100ms updates." + }, + { + "title": "Shared Ignore/Whitelist State", + "description": "Shared UserViewModel across IgnoreListScreen and WhitelistScreen navigation routes to update rules instantly on history screen re-entry." + } + ], + "fixes": [] + }, + "release_notes": { + "discord": "**Archipelago Alerts Android App v1.6.21 Released!**\n\n**New Features & Enhancements**\n\u2022 **Real-Time History Progress**: Track history sync status live with a dynamic percentage bar (`X% / 100%`) and clear progress indicators.\n\u2022 **Background History Syncing**: History syncing now continues seamlessly via WorkManager and ApplicationScope when screen is locked or app is minimized.\n\u2022 **Instant Ignore & Whitelist Updates**: Mute rules and whitelists update instantly when returning to the history screen without needing an app restart.\n\nUpdate now on Google Play or download the latest APK from GitHub Releases!", + "play_store": "", + "github": "" + } + }, + { + "version": "1.6.19", + "component": "app", + "component_label": "App", + "release_date": "2026-07-31", + "title": "Instant Slot Detail Navigation", + "highlights": [ + { + "title": "Instant Slot Detail Navigation", + "description": "Shared UserViewModel across navigation routes for immediate transition into slot details and player alias rendering." + }, + { + "title": "On-Demand Autocomplete Loading", + "description": "Deferred item/location autocomplete fetching until user interacts with dropdowns to eliminate initial screen load lag." + }, + { + "title": "Preferences UI Cleanup", + "description": "Removed duplicate help section from notification preferences screen." + } + ], + "categories": { + "features": [], + "improvements": [ + { + "title": "Instant Slot Detail Navigation", + "description": "Shared UserViewModel across navigation routes for immediate transition into slot details and player alias rendering." + }, + { + "title": "On-Demand Autocomplete Loading", + "description": "Deferred item/location autocomplete fetching until user interacts with dropdowns to eliminate initial screen load lag." + }, + { + "title": "Preferences UI Cleanup", + "description": "Removed duplicate help section from notification preferences screen." + } + ], + "fixes": [] + }, + "release_notes": { + "discord": "**Archipelago Alerts Android App v1.6.19 Released!**\n\n**Improvements & Fixes**\n\u2022 **Instant Slot Detail Navigation**: Zero-latency screen transitions when opening slot details with dynamic player alias support.\n\u2022 **On-Demand Autocomplete**: Lazy loading for item and location autocomplete options to accelerate screen loads.\n\u2022 **Preferences UI Cleanup**: Streamlined notification preference screens.\n\nUpdate now on Google Play or download the latest APK from GitHub Releases!", + "play_store": "", + "github": "" + } + }, + { + "version": "1.6.18", + "component": "app", + "component_label": "App", + "release_date": "2026-07-30", + "title": "Push Notification Whitelist & System Improvements", + "highlights": [ + { + "title": "Push Notification Whitelist", + "description": "Added WhitelistScreen UI allowing users to whitelist specific items or item groups to always receive notifications regardless of mute settings." + }, + { + "title": "What's New Dialog", + "description": "Interactive bottom sheet displaying release highlights upon app update." + }, + { + "title": "Cursor-Based History Sync", + "description": "Replaced timestamp-based history watermarks with integer cursors for faster sync and robust retry handling." + }, + { + "title": "History Job Cancellation", + "description": "In-flight refresh coroutines in HistoryViewModel are properly cancelled on repeated pull-to-refresh." + }, + { + "title": "Database Migration 20->21", + "description": "Automatically cleans up legacy timestamp watermarks upon Android app upgrade." + } + ], + "categories": { + "features": [ + { + "title": "Push Notification Whitelist", + "description": "Added WhitelistScreen UI allowing users to whitelist specific items or item groups to always receive notifications regardless of mute settings." + }, + { + "title": "What's New Dialog", + "description": "Interactive bottom sheet displaying release highlights upon app update." + } + ], + "improvements": [ + { + "title": "Cursor-Based History Sync", + "description": "Replaced timestamp-based history watermarks with integer cursors for faster sync and robust retry handling." + } + ], + "fixes": [ + { + "title": "History Job Cancellation", + "description": "In-flight refresh coroutines in HistoryViewModel are properly cancelled on repeated pull-to-refresh." + }, + { + "title": "Database Migration 20->21", + "description": "Automatically cleans up legacy timestamp watermarks upon Android app upgrade." + } + ] + }, + "release_notes": { + "discord": "**Archipelago Alerts Android App v1.6.18 Released!**\n\n**New Features**\n\u2022 **Push Notification Whitelist**: Want notifications for specific items (e.g. Progressive Swords, Bombs) even if filler/category mutes are enabled? You can now whitelist individual items or item groups per-game or globally!\n\n**Improvements & Fixes**\n\u2022 **Instant History Sync**: Refactored item history synchronization using cursor watermarks for faster load times and zero missing items.\n\u2022 **Item Index Tracking**: Received item ordering now tracks Archipelago's native item index for 100% item fidelity.\n\u2022 **Cheese Tracker Sync**: Improved slot claim validation and conflict resolution.\n\nUpdate now on Google Play or download the latest APK from GitHub Releases!", + "play_store": "", + "github": "" + } + }, + { + "version": "1.6.14", + "component": "app", + "component_label": "App", + "release_date": "2026-06-24", + "title": "App Release v1.6.14", + "highlights": [ + { + "title": "Cheese Tracker Slot Claim", + "description": "Fixed slot claim UI state syncing for unauthenticated slots." + } + ], + "categories": { + "features": [], + "improvements": [], + "fixes": [ + { + "title": "Cheese Tracker Slot Claim", + "description": "Fixed slot claim UI state syncing for unauthenticated slots." + } + ] + }, + "release_notes": { + "discord": "", + "play_store": "", + "github": "" + } + } + ], + "server_releases": [ + { + "version": "1.6.22", + "component": "server", + "component_label": "Server", + "release_date": "2026-08-03", + "title": "Cheese Tracker Notes & Statuses API", + "highlights": [ + { + "title": "Per-Slot Cheese State", + "description": "GET /api/user/tracked_slots now returns a cheese object per slot (notes, progression/completion status, ping, last checked, ownership)." + }, + { + "title": "Slot Editing Endpoint", + "description": "New PUT /rooms//slots//cheese to edit notes/status/ping and refresh Last Checked, with ownership checks and optimistic-conflict handling." + }, + { + "title": "Default Ping Preference", + "description": "cheese_default_ping is now applied at claim time, fixing the ping preference always defaulting to Never." + } + ], + "categories": { + "features": [ + { + "title": "Cheese Slot State (read)", + "description": "get_user_tracked_slots parses the room's cached Cheese Tracker data and attaches a per-slot cheese object (game_id, notes, progression_status, completion_status, discord_ping, last_checked, is_mine, global_ping_policy) for Cheese-connected users." + }, + { + "title": "Cheese Slot State (write)", + "description": "New synchronous PUT /rooms//slots//cheese endpoint. Validates enum values, re-fetches the tracker, enforces ownership, applies partial updates, stamps last_checked for BK/Soft BK and Still BK, sends x-if-owner-is as a conflict guard, and splices the authoritative response back into the room cache." + }, + { + "title": "User.cheese_default_ping", + "description": "New nullable column (Alembic a1c7e9f4b2d0) exposed on the user profile and settable via PUT /users/me/preferences." + } + ], + "improvements": [ + { + "title": "Claim-Time Ping Default", + "description": "send_state in api_cheese.py now applies the user's cheese_default_ping when claiming a slot, and aligns unclaim behavior with Cheese Tracker's web UI (availability to open, ping to never)." + } + ], + "fixes": [ + { + "title": "Ping Preference Stuck on Never", + "description": "Newly claimed slots now honor the user's chosen default ping preference instead of always defaulting to Never." + } + ] + }, + "release_notes": { + "discord": "**Archipelago Alerts Backend v1.6.22 Released!**\n\n**New: Cheese Tracker Notes & Statuses**\n\u2022 **Per-Slot State API**: `GET /api/user/tracked_slots` now includes a `cheese` object per slot (notes, progression/completion status, ping, last checked, ownership).\n\u2022 **Slot Editing**: New `PUT /rooms//slots//cheese` to edit notes/status/ping and refresh \"Last Checked\" (\"Still BK\"), with ownership checks and optimistic-conflict handling.\n\u2022 **Default Ping Preference**: `cheese_default_ping` is now applied at claim time, fixing the ping preference always defaulting to \"Never\".", + "play_store": "", + "github": "" + } + }, + { + "version": "1.6.21", + "component": "server", + "component_label": "Server", + "release_date": "2026-08-03", + "title": "Tracked Slot Item Count Aggregation", + "highlights": [ + { + "title": "Tracked Slot Item Count Aggregation", + "description": "Updated get_user_tracked_slots query in slots_routes.py to aggregate item_count per slot in the JSON response payload." + }, + { + "title": "Website Version Display Alignment", + "description": "Updated get_android_version() in utils.py to check changelog.json so the landing page version badges stay aligned with release notes across all environments." + } + ], + "categories": { + "features": [], + "improvements": [ + { + "title": "Tracked Slot Item Count Aggregation", + "description": "Updated get_user_tracked_slots query in slots_routes.py to aggregate item_count per slot in the JSON response payload." + }, + { + "title": "Website Version Display Alignment", + "description": "Updated get_android_version() in utils.py to check changelog.json so the landing page version badges stay aligned with release notes across all environments." + } + ], + "fixes": [] + }, + "release_notes": { + "discord": "**Archipelago Alerts Backend v1.6.21 Released!**\n\n**Improvements & Fixes**\n\u2022 **Tracked Slot Item Count Payload**: Surfaced total item counts per slot in `GET /api/user/tracked_slots` to drive client-side progress calculation.\n\u2022 **Landing Page Version Syncing**: Fixed landing page version badges to resolve from `changelog.json` in production containers.", + "play_store": "", + "github": "" + } + }, + { + "version": "1.6.19", + "component": "server", + "component_label": "Server", + "release_date": "2026-07-31", + "title": "Poller CPU & Resource Throttling", + "highlights": [ + { + "title": "Poller CPU & Resource Throttling", + "description": "Introduced db_process_semaphore (limit=3) to throttle concurrent synchronous database processing during room poll cycles, smoothing CPU usage and eliminating high-load CPU spikes." + }, + { + "title": "Cycle Jitter & Staggering", + "description": "Added per-cycle \u00b130s random jitter to the 5-minute poller sleep interval and expanded initial room stagger (1\u201360s) to prevent room polling tasks from re-synchronizing into waves over time." + }, + { + "title": "SQLAlchemy Connection Pool Tuning", + "description": "Configured pool settings (pool_size=10, max_overflow=5, pool_recycle=1800, pool_pre_ping=True) for PostgreSQL in production to avoid connection pool exhaustion under load." + }, + { + "title": "Docker Compose CPU & Memory Limits", + "description": "Defined resource limits and reservations for api and poller containers to guarantee API CPU availability (0.4 vCPU reserved for API, poller capped at 1.0 vCPU) on 2 vCPU VMs." + }, + { + "title": "Per-Game Datapackage Cache Lock", + "description": "Added an in-memory per-game asyncio lock in game_routes.py to prevent concurrent autocomplete queries from redundantly fetching game datapackages." + }, + { + "title": "Database Healthcheck Environment Escaping", + "description": "Escaped PostgreSQL env vars ($$POSTGRES_USER and $$POSTGRES_DB) in docker-compose.yml healthcheck so credentials resolve from the container's environment dynamically across dev, UAT, and prod." + } + ], + "categories": { + "features": [], + "improvements": [ + { + "title": "Poller CPU & Resource Throttling", + "description": "Introduced db_process_semaphore (limit=3) to throttle concurrent synchronous database processing during room poll cycles, smoothing CPU usage and eliminating high-load CPU spikes." + }, + { + "title": "Cycle Jitter & Staggering", + "description": "Added per-cycle \u00b130s random jitter to the 5-minute poller sleep interval and expanded initial room stagger (1\u201360s) to prevent room polling tasks from re-synchronizing into waves over time." + }, + { + "title": "SQLAlchemy Connection Pool Tuning", + "description": "Configured pool settings (pool_size=10, max_overflow=5, pool_recycle=1800, pool_pre_ping=True) for PostgreSQL in production to avoid connection pool exhaustion under load." + }, + { + "title": "Docker Compose CPU & Memory Limits", + "description": "Defined resource limits and reservations for api and poller containers to guarantee API CPU availability (0.4 vCPU reserved for API, poller capped at 1.0 vCPU) on 2 vCPU VMs." + }, + { + "title": "Per-Game Datapackage Cache Lock", + "description": "Added an in-memory per-game asyncio lock in game_routes.py to prevent concurrent autocomplete queries from redundantly fetching game datapackages." + } + ], + "fixes": [ + { + "title": "Database Healthcheck Environment Escaping", + "description": "Escaped PostgreSQL env vars ($$POSTGRES_USER and $$POSTGRES_DB) in docker-compose.yml healthcheck so credentials resolve from the container's environment dynamically across dev, UAT, and prod." + } + ] + }, + "release_notes": { + "discord": "**Archipelago Alerts Backend v1.6.19 Released!**\n\n**Improvements & Fixes**\n\u2022 **Poller CPU & Resource Throttling**: Throttled concurrent room processing cycles to smooth CPU spikes.\n\u2022 **Cycle Jitter & Staggering**: Added random jitter to poller sleep intervals to prevent wave synchronization.\n\u2022 **SQLAlchemy Pool Tuning**: Optimized PostgreSQL connection pool size and recycling for high concurrency.\n\u2022 **Datapackage Cache Locking**: Prevented concurrent autocomplete requests from redundant datapackage fetches.", + "play_store": "", + "github": "" + } + }, + { + "version": "1.6.18", + "component": "server", + "component_label": "Server", + "release_date": "2026-07-30", + "title": "GET /api/whats_new Endpoint & System Improvements", + "highlights": [ + { + "title": "GET /api/whats_new Endpoint", + "description": "Backend API to dynamically fetch release notes and patch highlights with target filtering (app, server, all)." + }, + { + "title": "Item & Group Whitelist Schema", + "description": "Introduced UserWhitelistItem backend model and database migrations." + }, + { + "title": "Native item_index Preservation", + "description": "Backend poller now logs and orders received items using Archipelago's native item_index sequence." + }, + { + "title": "Database Performance", + "description": "Added composite performance indexes for history queries and room subscription polling." + } + ], + "categories": { + "features": [ + { + "title": "GET /api/whats_new Endpoint", + "description": "Backend API to dynamically fetch release notes and patch highlights with target filtering (app, server, all)." + }, + { + "title": "Item & Group Whitelist Schema", + "description": "Introduced UserWhitelistItem backend model and database migrations." + } + ], + "improvements": [ + { + "title": "Native item_index Preservation", + "description": "Backend poller now logs and orders received items using Archipelago's native item_index sequence." + }, + { + "title": "Database Performance", + "description": "Added composite performance indexes for history queries and room subscription polling." + } + ], + "fixes": [] + }, + "release_notes": { + "discord": "", + "play_store": "", + "github": "" + } + }, + { + "version": "1.6.14", + "component": "server", + "component_label": "Server", + "release_date": "2026-06-24", + "title": "Server Release v1.6.14", + "highlights": [ + { + "title": "Milestone Groups Optimizations", + "description": "Improved the backend process that supplies items and item_groups for the Milestone Group builder." + } + ], + "categories": { + "features": [], + "improvements": [], + "fixes": [ + { + "title": "Milestone Groups Optimizations", + "description": "Improved the backend process that supplies items and item_groups for the Milestone Group builder." + } + ] + }, + "release_notes": { + "discord": "", + "play_store": "", + "github": "" + } + } + ] +} diff --git a/backend/app/routes/whats_new_routes.py b/backend/app/routes/whats_new_routes.py index 83d8536..7fa0540 100644 --- a/backend/app/routes/whats_new_routes.py +++ b/backend/app/routes/whats_new_routes.py @@ -1,38 +1,17 @@ -import os -import json -import logging from flask import Blueprint, jsonify, request +from app import changelog + whats_new_bp = Blueprint('whats_new_routes', __name__) -DATA_FILE_PATH = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'data', 'changelog.json') def _load_changelog_data(): - """Loads release notes from changelog.json file.""" - if not os.path.exists(DATA_FILE_PATH): - logging.warning(f"[WHATS_NEW] Changelog data file not found at {DATA_FILE_PATH}") - return { - "latest_version": "1.0.0", - "app_latest_version": "1.0.0", - "server_latest_version": "1.0.0", - "app_releases": [], - "server_releases": [], - "releases": [] - } - - try: - with open(DATA_FILE_PATH, 'r', encoding='utf-8') as f: - return json.load(f) - except Exception as e: - logging.error(f"[WHATS_NEW] Failed to read changelog data: {e}") - return { - "latest_version": "1.0.0", - "app_latest_version": "1.0.0", - "server_latest_version": "1.0.0", - "app_releases": [], - "server_releases": [], - "releases": [] - } + """ + Returns the full What's New payload derived from changelog.json: + the two component arrays plus the computed merged `releases` list and the + `*_latest_version` fields. + """ + return changelog.enrich() @whats_new_bp.route('/api/whats_new', methods=['GET']) def get_whats_new(): diff --git a/backend/app/utils.py b/backend/app/utils.py index 7cb8ff7..cb777d9 100644 --- a/backend/app/utils.py +++ b/backend/app/utils.py @@ -1,60 +1,34 @@ import logging import aiohttp import os -import re import ssl import certifi from urllib.parse import urlparse from datetime import timezone from app import Session +from app import changelog from app.models import TrackedRoom CHEESE_USER_AGENT_BASE = 'ArchipelagoAlerts' CHEESE_CONTACT = 'github.com/wrjones104' def get_server_version(): - """Extracts server version from APP_VERSION env var, backend/VERSION file, or VERSION file.""" + """Server version: APP_VERSION env override, else newest server changelog entry.""" env_version = os.environ.get("APP_VERSION") if env_version: return env_version.lstrip("v") - # Try backend/VERSION or root VERSION - for path in ['../VERSION', '../../backend/VERSION', '../../VERSION']: - version_file = os.path.join(os.path.dirname(__file__), path) - if os.path.exists(version_file): - try: - with open(version_file, 'r') as f: - ver = f.read().strip() - if ver: - return ver.lstrip("v") - except Exception: - pass + version = changelog.latest_version("server") + if version: + return version.lstrip("v") - return "1.6.19" + return changelog.FALLBACK_VERSION def get_android_version(): - """Extracts Android app version from build.gradle.kts, changelog.json, or backend VERSION.""" - try: - gradle_path = os.path.join(os.path.dirname(__file__), '../../android/app/build.gradle.kts') - if os.path.exists(gradle_path): - with open(gradle_path, 'r') as f: - content = f.read() - match = re.search(r'versionName\s*=\s*"([^"]+)"', content) - if match: - return match.group(1) - except Exception as e: - logging.warning(f"[VERSION] Could not read version from gradle: {e}") - - try: - import json - changelog_path = os.path.join(os.path.dirname(__file__), 'data/changelog.json') - if os.path.exists(changelog_path): - with open(changelog_path, 'r') as f: - data = json.load(f) - if 'app_latest_version' in data and data['app_latest_version']: - return data['app_latest_version'] - except Exception as e: - logging.warning(f"[VERSION] Could not read app version from changelog.json: {e}") + """Android app version: newest app changelog entry (single source of truth).""" + version = changelog.latest_version("app") + if version: + return version.lstrip("v") return get_server_version() diff --git a/backend/tests/test_whats_new.py b/backend/tests/test_whats_new.py index 9ca614c..5316950 100644 --- a/backend/tests/test_whats_new.py +++ b/backend/tests/test_whats_new.py @@ -13,10 +13,10 @@ from app import create_app -# The whats_new routes read backend/app/data/changelog.json, which is a -# gitignored generated artifact and therefore absent on fresh checkouts (e.g. -# CI). These tests provide their own known changelog data so they don't depend -# on that deploy-time file. +# These tests provide their own known changelog data (by patching the route's +# loader) so they exercise the endpoints against fixed content rather than the +# real, evolving backend/app/data/changelog.json. The fixture mirrors the +# enriched payload shape that app.changelog.enrich() produces at runtime. FIXTURE_CHANGELOG = { "latest_version": "1.6.18", "app_latest_version": "1.6.18", diff --git a/scripts/generate_changelog.py b/scripts/generate_changelog.py new file mode 100644 index 0000000..7a96778 --- /dev/null +++ b/scripts/generate_changelog.py @@ -0,0 +1,203 @@ +#!/usr/bin/env python3 +""" +generate_changelog.py +--------------------- +`backend/app/data/changelog.json` is the single source of truth for release +notes (hand-edited, one `app_releases` array and one `server_releases` array, +each newest-first). This script *derives* the human-readable markdown from it: + + android/CHANGELOG.md <- app_releases + backend/CHANGELOG.md <- server_releases + +Those markdown files are generated artifacts — do not edit them by hand. + +Usage: + python scripts/generate_changelog.py # (re)write the CHANGELOG.md files + python scripts/generate_changelog.py --check # verify, don't write (CI / pre-commit) + +--check exits non-zero when: + * a generated CHANGELOG.md is stale (json changed but markdown wasn't regenerated), or + * android/app/build.gradle.kts versionName != newest app_releases version. + +The gradle check is the one guardrail that catches the single mistake still +possible under this system: bumping the Android build version without adding a +matching changelog entry (or vice versa). +""" + +import os +import re +import sys +import json + +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +ROOT_DIR = os.path.abspath(os.path.join(SCRIPT_DIR, '..')) + +CHANGELOG_JSON = os.path.join(ROOT_DIR, 'backend', 'app', 'data', 'changelog.json') +ANDROID_MD = os.path.join(ROOT_DIR, 'android', 'CHANGELOG.md') +BACKEND_MD = os.path.join(ROOT_DIR, 'backend', 'CHANGELOG.md') +GRADLE_FILE = os.path.join(ROOT_DIR, 'android', 'app', 'build.gradle.kts') + +GENERATED_HEADER = ( + "\n" +) + +CATEGORY_HEADINGS = [ + ("features", "Added"), + ("improvements", "Changed"), + ("fixes", "Fixed"), +] + + +def load_source(): + with open(CHANGELOG_JSON, 'r', encoding='utf-8') as f: + data = json.load(f) + return data.get("app_releases", []), data.get("server_releases", []) + + +def render_markdown(title, releases): + """Render one component's releases into a Keep a Changelog style markdown doc.""" + lines = [GENERATED_HEADER, f"# {title}", ""] + lines.append( + "All notable changes to the **" + title.replace(" Changelog", "") + + "** are documented in this file." + ) + lines.append("") + lines.append( + "The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), " + "and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html)." + ) + lines.append("") + lines.append("> This file is generated from `backend/app/data/changelog.json`.") + lines.append("") + + for rel in releases: + version = rel.get("version", "") + date = rel.get("release_date", "") + header = f"## [{version}]" + if date: + header += f" - {date}" + lines.append(header) + lines.append("") + + rel_title = rel.get("title") + if rel_title: + lines.append(f"_{rel_title}_") + lines.append("") + + release_notes = rel.get("release_notes") or {} + channel_labels = [ + ("discord", "Discord"), + ("play_store", "Play Console — What's New"), + ("github", "GitHub Release"), + ] + for key, label in channel_labels: + snippet = release_notes.get(key) + if not snippet: + continue + lines.append(f"> **{label} Copy-Paste:**") + lines.append("> ```markdown") + for dl in snippet.split("\n"): + lines.append(f"> {dl}".rstrip()) + lines.append("> ```") + lines.append("") + + categories = rel.get("categories", {}) or {} + for key, heading in CATEGORY_HEADINGS: + items = categories.get(key) or [] + if not items: + continue + lines.append(f"### {heading}") + for item in items: + item_title = item.get("title", "").strip() + desc = item.get("description", "").strip() + if desc and desc != item_title: + lines.append(f"- **{item_title}**: {desc}") + else: + lines.append(f"- **{item_title}**") + lines.append("") + + lines.append("---") + lines.append("") + + # Trim trailing separator/blank lines for a clean end-of-file. + while lines and lines[-1] in ("", "---"): + lines.pop() + return "\n".join(lines) + "\n" + + +def read_gradle_version(): + if not os.path.exists(GRADLE_FILE): + return None + with open(GRADLE_FILE, 'r', encoding='utf-8') as f: + match = re.search(r'versionName\s*=\s*"([^"]+)"', f.read()) + return match.group(1) if match else None + + +def write_file(path, content): + with open(path, 'w', encoding='utf-8', newline='\n') as f: + f.write(content) + + +def main(): + check_only = "--check" in sys.argv[1:] + app_releases, server_releases = load_source() + + targets = [ + (ANDROID_MD, render_markdown("Android App Changelog", app_releases)), + (BACKEND_MD, render_markdown("Backend Server Changelog", server_releases)), + ] + + problems = [] + + # 1. Markdown freshness + for path, expected in targets: + current = "" + if os.path.exists(path): + with open(path, 'r', encoding='utf-8', newline='\n') as f: + current = f.read() + if check_only: + if current != expected: + problems.append( + f"{os.path.relpath(path, ROOT_DIR)} is stale — " + f"run: python scripts/generate_changelog.py" + ) + else: + write_file(path, expected) + print(f"Wrote {os.path.relpath(path, ROOT_DIR)}") + + # 2. Gradle / changelog version agreement (Android) + app_latest = app_releases[0]["version"] if app_releases else None + gradle_version = read_gradle_version() + if app_latest and gradle_version and gradle_version != app_latest: + problems.append( + f"Version mismatch: build.gradle.kts versionName is {gradle_version} " + f"but newest app changelog entry is {app_latest}. " + f"Add a {gradle_version} entry to changelog.json or align the versions." + ) + + if check_only: + if problems: + print("Changelog check FAILED:", file=sys.stderr) + for p in problems: + print(f" - {p}", file=sys.stderr) + return 1 + print("Changelog check passed.") + return 0 + + # In write mode, still surface the gradle mismatch as a warning (non-fatal). + if problems: + print("\nWarnings:") + for p in problems: + print(f" ! {p}") + + app_v = app_releases[0]["version"] if app_releases else "?" + server_v = server_releases[0]["version"] if server_releases else "?" + print(f"App latest: v{app_v} ({len(app_releases)} entries) | " + f"Server latest: v{server_v} ({len(server_releases)} entries)") + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/scripts/sync_changelog.py b/scripts/sync_changelog.py deleted file mode 100644 index 5d5303a..0000000 --- a/scripts/sync_changelog.py +++ /dev/null @@ -1,179 +0,0 @@ -#!/usr/bin/env python3 -""" -sync_changelog.py ------------------ -Parses android/CHANGELOG.md and backend/CHANGELOG.md (or root CHANGELOG.md) -and updates backend/app/data/changelog.json automatically. - -Usage: - python scripts/sync_changelog.py -""" - -import os -import re -import json - -SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) -ROOT_DIR = os.path.abspath(os.path.join(SCRIPT_DIR, '..')) - -ANDROID_CHANGELOG_MD = os.path.join(ROOT_DIR, 'android', 'CHANGELOG.md') -BACKEND_CHANGELOG_MD = os.path.join(ROOT_DIR, 'backend', 'CHANGELOG.md') -ROOT_CHANGELOG_MD = os.path.join(ROOT_DIR, 'CHANGELOG.md') - -if os.path.exists(os.path.join(ROOT_DIR, 'backend', 'app', 'data')): - CHANGELOG_JSON_PATH = os.path.join(ROOT_DIR, 'backend', 'app', 'data', 'changelog.json') -else: - CHANGELOG_JSON_PATH = os.path.join(ROOT_DIR, 'app', 'data', 'changelog.json') - - -def clean_markdown_inline(text): - """Cleans inline markdown formatting for JSON descriptions while preserving text.""" - if not text: - return "" - text = re.sub(r'`([^`]+)`', r'\1', text) - return text.strip() - - -def parse_changelog_md(md_content, component="all"): - """ - Parses a CHANGELOG.md section into structured release objects. - component: 'app', 'server', or 'all' - """ - releases = [] - - # Split content into version blocks (e.g. ## [1.6.18] - 2026-07-30) - version_blocks = re.split(r'\n##\s+\[([\d\.]+(?:-[\w\.]+)?)\](?:\s*-\s*([^\n]+))?', md_content) - - idx = 1 - while idx < len(version_blocks): - version = version_blocks[idx].strip() - release_date = version_blocks[idx+1].strip() if version_blocks[idx+1] else "" - block_text = version_blocks[idx+2] if idx+2 < len(version_blocks) else "" - idx += 3 - - # Extract Discord Copy-Paste block if present - discord_md = "" - discord_match = re.search(r'>\s*```markdown\n(.*?)\n>\s*```', block_text, re.DOTALL) - if discord_match: - discord_lines = [line.lstrip('> ').rstrip() for line in discord_match.group(1).split('\n')] - discord_md = '\n'.join(discord_lines).strip() - - # Extract Category Sections (### Added, ### Changed, ### Fixed, etc.) - features = [] - improvements = [] - fixes = [] - highlights = [] - - category_blocks = re.split(r'\n###\s+([^\n]+)', block_text) - cat_idx = 1 - while cat_idx < len(category_blocks): - cat_name = category_blocks[cat_idx].strip().lower() - cat_text = category_blocks[cat_idx+1] if cat_idx+1 < len(category_blocks) else "" - cat_idx += 2 - - # Parse bullet points (- **Title**: Description) - items = [] - for line in cat_text.split('\n'): - line = line.strip() - if line.startswith('- ') or line.startswith('* '): - clean_line = line[2:].strip() - parts = clean_line.split(':', 1) - if len(parts) == 2: - raw_title = parts[0].strip().replace('**', '') - title = clean_markdown_inline(raw_title) - desc = clean_markdown_inline(parts[1].strip()) - else: - raw_title = clean_line.replace('**', '') - title = clean_markdown_inline(raw_title) - desc = title - - item_obj = {"title": title, "description": desc} - items.append(item_obj) - highlights.append({"title": title, "description": desc}) - - if "add" in cat_name or "feature" in cat_name: - features.extend(items) - elif "change" in cat_name or "improve" in cat_name: - improvements.extend(items) - elif "fix" in cat_name: - fixes.extend(items) - - comp_label = "App" if component == "app" else ("Server" if component == "server" else "") - - # Title heuristic - title = "" - if features: - title = features[0]['title'] - if len(features) > 1 or improvements: - title += " & System Improvements" - elif improvements: - title = improvements[0]['title'] - else: - title = f"{comp_label} Release v{version}".strip() - - release_entry = { - "version": version, - "component": component, - "component_label": comp_label, - "release_date": release_date, - "title": title, - "highlights": highlights, - "categories": { - "features": features, - "improvements": improvements, - "fixes": fixes - }, - "discord_md": discord_md - } - releases.append(release_entry) - - return releases - - -def main(): - app_releases = [] - server_releases = [] - - if os.path.exists(ANDROID_CHANGELOG_MD): - with open(ANDROID_CHANGELOG_MD, 'r', encoding='utf-8') as f: - app_releases = parse_changelog_md(f.read(), component="app") - - if os.path.exists(BACKEND_CHANGELOG_MD): - with open(BACKEND_CHANGELOG_MD, 'r', encoding='utf-8') as f: - server_releases = parse_changelog_md(f.read(), component="server") - - # Fallback to root CHANGELOG.md if neither specific changelog exists - if not app_releases and not server_releases and os.path.exists(ROOT_CHANGELOG_MD): - with open(ROOT_CHANGELOG_MD, 'r', encoding='utf-8') as f: - combined = parse_changelog_md(f.read(), component="all") - app_releases = combined - server_releases = combined - - app_latest = app_releases[0]["version"] if app_releases else "1.0.0" - server_latest = server_releases[0]["version"] if server_releases else "1.0.0" - - # Merge all releases sorted by release_date descending - all_releases = app_releases + server_releases - all_releases.sort(key=lambda r: (r.get("release_date", ""), r.get("version", "")), reverse=True) - - data = { - "latest_version": server_latest, - "app_latest_version": app_latest, - "server_latest_version": server_latest, - "app_releases": app_releases, - "server_releases": server_releases, - "releases": all_releases - } - - os.makedirs(os.path.dirname(CHANGELOG_JSON_PATH), exist_ok=True) - with open(CHANGELOG_JSON_PATH, 'w', encoding='utf-8') as f: - json.dump(data, f, indent=2) - - print(f"Successfully synced changelogs -> {CHANGELOG_JSON_PATH}") - print(f"App Latest Version: v{app_latest} ({len(app_releases)} releases)") - print(f"Server Latest Version: v{server_latest} ({len(server_releases)} releases)") - print(f"Total Combined Releases: {len(all_releases)}") - - -if __name__ == '__main__': - main() From 247e37def89dc8cc0eeaf583fcdd415e22706faa Mon Sep 17 00:00:00 2001 From: wrjones104 Date: Tue, 4 Aug 2026 10:17:55 -0400 Subject: [PATCH 2/5] Remove stray artifact directory and stale deployment guide android.89be56d7/ was a leftover artifact directory from another tool; PROD_DEPLOYMENT_GUIDE.md is being retired as documentation. Co-Authored-By: Claude Sonnet 5 --- PROD_DEPLOYMENT_GUIDE.md | 156 ------------------ .../implementation_plan.artifact.md | 28 ---- 2 files changed, 184 deletions(-) delete mode 100644 PROD_DEPLOYMENT_GUIDE.md delete mode 100644 android.89be56d7/.artifacts/f785dcbb-d054-40b8-ab59-49aa1bf85aa0/implementation_plan.artifact.md diff --git a/PROD_DEPLOYMENT_GUIDE.md b/PROD_DEPLOYMENT_GUIDE.md deleted file mode 100644 index 6d799bc..0000000 --- a/PROD_DEPLOYMENT_GUIDE.md +++ /dev/null @@ -1,156 +0,0 @@ -# Production & UAT Deployment Guide — Archipelago Alerts - -This guide documents the step-by-step deployment procedure and key **"gotchas"** learned during the architecture overhaul and Docker containerization of **Archipelago Alerts**. Use this reference when deploying to Production or new GCP VM environments. - ---- - -## Architecture Summary -* **API Server (`api`):** Flask / Waitress WSGI application listening on port `5000`. -* **Background Worker (`poller`):** Event-driven room polling service running Redis Pub/Sub listener and exponential setup backoff. -* **Cache & Event Queue (`redis`):** Containerized Redis 7 listening on port `6379`. -* **Database (`postgres`):** Host-level native PostgreSQL (port `5432`) or containerized PostgreSQL (port `5433`). -* **Reverse Proxy (`nginx`):** Host-level Nginx listening on ports `80`/`443` with SSL termination, forwarding traffic to `http://127.0.0.1:5000`. - ---- - -## Production Deployment Checklist - -### Step 1: Clone / Pull Repository -```bash -cd /var/www/ap-tracker -git fetch origin -git checkout main # or feature branch -git pull origin main -``` - ---- - -### Step 2: Configure Host PostgreSQL Permissions (Crucial for `host.docker.internal`) - -If connecting Docker containers to your existing native PostgreSQL database on port `5432`: - -#### 1. Update `postgresql.conf` -Edit your PostgreSQL config (e.g. `/etc/postgresql/15/main/postgresql.conf`): -```ini -# Change listen_addresses from 'localhost' to '*' -listen_addresses = '*' -``` - -#### 2. Update `pg_hba.conf` -Edit your authentication config (e.g. `/etc/postgresql/15/main/pg_hba.conf`) and append: -```ini -# Allow connections from Docker network subnet -host all all 172.16.0.0/12 md5 -host all all 172.16.0.0/12 scram-sha-256 -``` - -#### 3. Restart PostgreSQL -```bash -sudo systemctl restart postgresql -``` - ---- - -### Step 3: Populate `backend/.env` Secrets - -Ensure `backend/.env` contains all required credentials: -```ini -# Database Credentials & Connection String (Host/Container PostgreSQL) -POSTGRES_USER=ap_tracker_prod_user -POSTGRES_PASSWORD=ap_password -POSTGRES_DB=ap_tracker_prod -DATABASE_URL=postgresql://ap_tracker_prod_user:ap_password@host.docker.internal:5432/ap_tracker_prod - -# Redis Connection String (Containerized Redis) -REDIS_URL=redis://redis:6379/0 - -# Flask Environment -FLASK_ENV=production - -# Mandatory Encryption & Auth Secrets -SECRET_KEY= -ENCRYPTION_KEY= - -# Discord OAuth Application Credentials -DISCORD_CLIENT_ID= -DISCORD_CLIENT_SECRET= - -# Cheese Tracker Base URL (Optional override) -CHEESE_BASE_URL=https://cheesetrackers.theincrediblewheelofchee.se/api -``` - ---- - -### Step 4: Stop Native Systemd Services -Prevent port collisions on host ports `5000` and `6379`: -```bash -sudo systemctl stop ap-tracker-api || true -sudo systemctl stop ap-tracker-poller || true -sudo systemctl disable ap-tracker-api || true -sudo systemctl disable ap-tracker-poller || true -``` - ---- - -### Step 5: Launch Containers & Apply Database Migrations -```bash -# 1. Build and start containers -docker compose up -d --build - -# 2. Run Alembic database migrations -docker compose exec api alembic upgrade head - -# 3. Verify single migration head -docker compose exec api alembic current -# Expected Output: 960bbde6606b (head) (mergepoint) - -# 4. Verify database engine connection -docker compose exec api python -c "from app import engine; print(engine.connect())" -``` - ---- - -### Step 6: Configure Host Nginx Reverse Proxy -Ensure `/etc/nginx/sites-available/ap-tracker` points to `http://127.0.0.1:5000`: -```nginx -server { - server_name archipelagoalerts.com www.archipelagoalerts.com; - - location / { - proxy_pass http://127.0.0.1:5000; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } -} -``` - -Reload Nginx: -```bash -sudo nginx -t -sudo systemctl reload nginx -``` - ---- - -## Known Gotchas & Trouble-Shooting Reference - -| Symptom / Error | Root Cause | Solution | -| :--- | :--- | :--- | -| **`502 Bad Gateway` from Nginx** | Nginx `proxy_pass` points to an old unix socket or stopped service. | Set `proxy_pass http://127.0.0.1:5000;` in Nginx site config and run `sudo systemctl reload nginx`. | -| **`Connection refused` on `host.docker.internal:5432`** | Native PostgreSQL on Linux listens only on `127.0.0.1`. | Set `listen_addresses = '*'` in `postgresql.conf`, add `172.16.0.0/12` to `pg_hba.conf`, and `sudo systemctl restart postgresql`. | -| **`400 BAD REQUEST` on Discord Login** | Missing or blank `DISCORD_CLIENT_ID` / `DISCORD_CLIENT_SECRET` in `backend/.env`. | Fill in Discord OAuth app credentials in `backend/.env` and restart containers (`docker compose up -d`). | -| **`alembic upgrade head` Multiple Heads Error** | Parallel migrations on separate branches (e.g. filler traps vs iOS platform). | Merge revision `960bbde6606b_merge_filler_trap_and_ios_platform_heads.py` unifies them. Always run `alembic upgrade head` (singular). | -| **`No space left on device` during Docker build** | Large `venv/` or `.git/` being sent in Docker context payload (145MB+). | Root `.dockerignore` ignores `venv/`, `.git/`, shrinking payload to < 50KB. Also run `docker system prune -af`. | -| **Debian 12 Apt 404 for Docker Repo** | Apt sources pointing to `ubuntu` repo URL on a Debian VM. | Set Docker Apt source URL to `https://download.docker.com/linux/debian` using `$VERSION_CODENAME`. | -| **Docker Compose Overriding `.env` DB URL** | Hardcoded default expression in `docker-compose.yml`. | Keep `env_file: - backend/.env` without hardcoded `${DATABASE_URL:-...}` fallbacks. | - ---- - -## Live Monitoring Commands - -* **Live Poller Logs:** `docker compose logs -f poller` -* **Live API Logs:** `docker compose logs -f api` -* **All Service Logs:** `docker compose logs -f` -* **Container Health:** `docker compose ps` diff --git a/android.89be56d7/.artifacts/f785dcbb-d054-40b8-ab59-49aa1bf85aa0/implementation_plan.artifact.md b/android.89be56d7/.artifacts/f785dcbb-d054-40b8-ab59-49aa1bf85aa0/implementation_plan.artifact.md deleted file mode 100644 index 7df740b..0000000 --- a/android.89be56d7/.artifacts/f785dcbb-d054-40b8-ab59-49aa1bf85aa0/implementation_plan.artifact.md +++ /dev/null @@ -1,28 +0,0 @@ -# Suggestions for Improving TutorialGuideScreen.kt - -This plan refactors the `TutorialGuideScreen` to improve performance, maintainability, and user experience by moving to a `LazyColumn` architecture, hoisting state for a better accordion experience, and isolating data from the UI layer. - -## Proposed Changes - -### UI & Architecture Improvements - -#### [MODIFY] [TutorialGuideScreen.kt](file:///C:/Projects/ap-tracker/android/app/src/main/java/com/jones/aptracker/ui/TutorialGuideScreen.kt) -- **Move Data Out of Composable**: Extract the hardcoded FAQ list to a companion object or a static provider. This prepares the code for localization and keeps the UI logic clean. -- **Switch to `LazyColumn`**: Replace the `Column` + `verticalScroll` with a `LazyColumn`. While the current list is short, `LazyColumn` is more idiomatic for lists in Compose and handles larger datasets efficiently. -- **Hoisted Accordion State**: Modify `FaqAccordionCard` to accept an `isExpanded` boolean and an `onClick` lambda. In `TutorialGuideScreen`, track the `expandedTopicId` so that expanding one topic automatically collapses others, providing a cleaner UI. -- **Component Extraction**: Extract the "Intro Card" into a private `@Composable` function to reduce nesting in the main screen. - -### Clean Code & Best Practices -- **Localization**: (Recommended) Move all hardcoded strings to `res/values/strings.xml`. -- **Theme Consistency**: Use `MaterialTheme` colors consistently (already mostly done, but ensuring all hardcoded `copy(alpha = ...)` values are justified). - -## Verification Plan - -### Automated Tests -- N/A (UI refactor focus). - -### Manual Verification -- Deploy to device/emulator. -- Verify that clicking a topic expands it. -- Verify that expanding a new topic collapses the previously expanded one. -- Scroll through the list to ensure `LazyColumn` rendering is smooth. From 11838507e27551314e10c94148696d9a15f55ecd Mon Sep 17 00:00:00 2001 From: wrjones104 Date: Tue, 4 Aug 2026 10:36:38 -0400 Subject: [PATCH 3/5] Add app v1.6.23 changelog entry, remove dead version-reading duplicate Reconciles the pending build.gradle.kts version bump (1.6.22 -> 1.6.23) with a changelog entry covering what actually shipped since 1.6.22: server-side isIgnored/isWhitelisted computation (fixing item-group filtering and per-game hint scoping), and the collapsed whitelist/ignore sub-menu on the history item detail sheet. Drafted with the new release-notes skill; generate_changelog.py --check now passes clean. Also removes poller.py's own unused, buggy copy of get_app_version() (read build.gradle.kts directly, never called) -- the canonical version lives in app/changelog.py via app.utils now. Co-Authored-By: Claude Sonnet 5 --- android/CHANGELOG.md | 45 +++++++++++++++++++++++ android/app/build.gradle.kts | 4 +-- backend/app/data/changelog.json | 63 ++++++++++++++++++++++++++++----- backend/app/poller.py | 14 -------- 4 files changed, 101 insertions(+), 25 deletions(-) diff --git a/android/CHANGELOG.md b/android/CHANGELOG.md index 46540ac..fe4ffba 100644 --- a/android/CHANGELOG.md +++ b/android/CHANGELOG.md @@ -10,6 +10,51 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), > This file is generated from `backend/app/data/changelog.json`. +## [1.6.23] - 2026-08-04 + +_Item Group Filtering Fixes & Cleaner Ignore Menu_ + +> **Discord Copy-Paste:** +> ```markdown +> **Archipelago Alerts Android App v1.6.23 Released!** +> +> **Fixes** +> • **Item Group Filtering**: Ignoring or whitelisting an item *group* now actually hides/shows those items in your History — previously these rules were silently ignored there. +> • **Hint Filtering**: Hints now correctly respect per-game ignore/whitelist rules instead of applying them across every game. +> +> **Improvements** +> • **Cleaner Ignore/Whitelist Menu**: The item detail sheet now uses two simple "Whitelist..." / "Ignore..." buttons that expand into the full options, instead of a wall of buttons. +> +> GitHub: +> Play Store: +> ``` + +> **Play Console — What's New Copy-Paste:** +> ```markdown +> Fixed: ignoring or whitelisting an item group now actually hides/shows those items in History and hints (previously had no effect there). Hint filtering also now respects per-game rules instead of applying across every game. +> +> Improved: the whitelist/ignore menu on item details is now two simple buttons instead of a long list. +> ``` + +> **GitHub Release Copy-Paste:** +> ```markdown +> ### Fixed +> - **Item Group Ignore/Whitelist Filtering**: Item-group ignore/whitelist rules are now computed server-side (`filtering_service.py`) and returned per item/hint via `isIgnored`/`isWhitelisted` fields, fixing group rules having no effect in the History "Show ignored items" filter. +> - **Hint Filtering Game Scope**: Hint filtering now correctly respects the game-specific scope of ignore/whitelist rules instead of applying them across all games. +> +> ### Changed +> - **Consolidated Ignore/Whitelist Menu**: The History item detail sheet's 6+ whitelist/ignore action buttons are now collapsed into two entry buttons that expand in-place to the scoped options. +> ``` + +### Changed +- **Cleaner Whitelist & Ignore Menu**: The whitelist and ignore options on an item's detail screen are now two simple buttons that expand into the full set of choices, instead of a long wall of buttons. + +### Fixed +- **Item Group Ignore/Whitelist Now Applies in History**: Ignoring or whitelisting an item group now correctly hides or shows those items in your History; previously group-based rules had no effect there. +- **Hint Filtering Respects Per-Game Rules**: Hints now correctly honor game-specific ignore/whitelist rules instead of applying them across all your games. + +--- + ## [1.6.22] - 2026-08-03 _Cheese Tracker Notes, Statuses & Ping Preferences_ diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index fc6faf8..9b51144 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -23,8 +23,8 @@ android { applicationId = "com.jones.aptracker" minSdk = 26 targetSdk = 36 - versionCode = 65 - versionName = "1.6.22" + versionCode = 66 + versionName = "1.6.23" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" manifestPlaceholders["appAuthRedirectScheme"] = "com.jones.aptracker" buildConfigField( diff --git a/backend/app/data/changelog.json b/backend/app/data/changelog.json index e246431..bce9ac5 100644 --- a/backend/app/data/changelog.json +++ b/backend/app/data/changelog.json @@ -1,5 +1,50 @@ { "app_releases": [ + { + "version": "1.6.23", + "component": "app", + "component_label": "App", + "release_date": "2026-08-04", + "title": "Item Group Filtering Fixes & Cleaner Ignore Menu", + "highlights": [ + { + "title": "Item Group Ignore/Whitelist Now Applies in History", + "description": "Ignoring or whitelisting an item group now correctly hides or shows those items in your History; previously group-based rules had no effect there." + }, + { + "title": "Hint Filtering Respects Per-Game Rules", + "description": "Hints now correctly honor game-specific ignore/whitelist rules instead of applying them across all your games." + }, + { + "title": "Cleaner Whitelist & Ignore Menu", + "description": "The whitelist and ignore options on an item's detail screen are now two simple buttons that expand into the full set of choices, instead of a long wall of buttons." + } + ], + "categories": { + "features": [], + "improvements": [ + { + "title": "Cleaner Whitelist & Ignore Menu", + "description": "The whitelist and ignore options on an item's detail screen are now two simple buttons that expand into the full set of choices, instead of a long wall of buttons." + } + ], + "fixes": [ + { + "title": "Item Group Ignore/Whitelist Now Applies in History", + "description": "Ignoring or whitelisting an item group now correctly hides or shows those items in your History; previously group-based rules had no effect there." + }, + { + "title": "Hint Filtering Respects Per-Game Rules", + "description": "Hints now correctly honor game-specific ignore/whitelist rules instead of applying them across all your games." + } + ] + }, + "release_notes": { + "discord": "**Archipelago Alerts Android App v1.6.23 Released!**\n\n**Fixes**\n• **Item Group Filtering**: Ignoring or whitelisting an item *group* now actually hides/shows those items in your History — previously these rules were silently ignored there.\n• **Hint Filtering**: Hints now correctly respect per-game ignore/whitelist rules instead of applying them across every game.\n\n**Improvements**\n• **Cleaner Ignore/Whitelist Menu**: The item detail sheet now uses two simple \"Whitelist...\" / \"Ignore...\" buttons that expand into the full options, instead of a wall of buttons.\n\nGitHub: \nPlay Store: ", + "play_store": "Fixed: ignoring or whitelisting an item group now actually hides/shows those items in History and hints (previously had no effect there). Hint filtering also now respects per-game rules instead of applying across every game.\n\nImproved: the whitelist/ignore menu on item details is now two simple buttons instead of a long list.", + "github": "### Fixed\n- **Item Group Ignore/Whitelist Filtering**: Item-group ignore/whitelist rules are now computed server-side (`filtering_service.py`) and returned per item/hint via `isIgnored`/`isWhitelisted` fields, fixing group rules having no effect in the History \"Show ignored items\" filter.\n- **Hint Filtering Game Scope**: Hint filtering now correctly respects the game-specific scope of ignore/whitelist rules instead of applying them across all games.\n\n### Changed\n- **Consolidated Ignore/Whitelist Menu**: The History item detail sheet's 6+ whitelist/ignore action buttons are now collapsed into two entry buttons that expand in-place to the scoped options." + } + }, { "version": "1.6.22", "component": "app", @@ -53,7 +98,7 @@ ] }, "release_notes": { - "discord": "**Archipelago Alerts v1.6.22 \u2014 Cheese Tracker Notes & Statuses**\n\n\u2022 View & edit Cheese Tracker notes and status (Unknown / Unblocked / BK / Soft BK / Go Mode) per slot\n\u2022 \"Still BK\" button to refresh your Last Checked time\n\u2022 Per-slot ping preference + a default ping for newly claimed slots\n\u2022 Default ping no longer stuck on \"Never\" when you claim a slot", + "discord": "**Archipelago Alerts v1.6.22 — Cheese Tracker Notes & Statuses**\n\n• View & edit Cheese Tracker notes and status (Unknown / Unblocked / BK / Soft BK / Go Mode) per slot\n• \"Still BK\" button to refresh your Last Checked time\n• Per-slot ping preference + a default ping for newly claimed slots\n• Default ping no longer stuck on \"Never\" when you claim a slot", "play_store": "", "github": "" } @@ -106,7 +151,7 @@ "fixes": [] }, "release_notes": { - "discord": "**Archipelago Alerts Android App v1.6.21 Released!**\n\n**New Features & Enhancements**\n\u2022 **Real-Time History Progress**: Track history sync status live with a dynamic percentage bar (`X% / 100%`) and clear progress indicators.\n\u2022 **Background History Syncing**: History syncing now continues seamlessly via WorkManager and ApplicationScope when screen is locked or app is minimized.\n\u2022 **Instant Ignore & Whitelist Updates**: Mute rules and whitelists update instantly when returning to the history screen without needing an app restart.\n\nUpdate now on Google Play or download the latest APK from GitHub Releases!", + "discord": "**Archipelago Alerts Android App v1.6.21 Released!**\n\n**New Features & Enhancements**\n• **Real-Time History Progress**: Track history sync status live with a dynamic percentage bar (`X% / 100%`) and clear progress indicators.\n• **Background History Syncing**: History syncing now continues seamlessly via WorkManager and ApplicationScope when screen is locked or app is minimized.\n• **Instant Ignore & Whitelist Updates**: Mute rules and whitelists update instantly when returning to the history screen without needing an app restart.\n\nUpdate now on Google Play or download the latest APK from GitHub Releases!", "play_store": "", "github": "" } @@ -150,7 +195,7 @@ "fixes": [] }, "release_notes": { - "discord": "**Archipelago Alerts Android App v1.6.19 Released!**\n\n**Improvements & Fixes**\n\u2022 **Instant Slot Detail Navigation**: Zero-latency screen transitions when opening slot details with dynamic player alias support.\n\u2022 **On-Demand Autocomplete**: Lazy loading for item and location autocomplete options to accelerate screen loads.\n\u2022 **Preferences UI Cleanup**: Streamlined notification preference screens.\n\nUpdate now on Google Play or download the latest APK from GitHub Releases!", + "discord": "**Archipelago Alerts Android App v1.6.19 Released!**\n\n**Improvements & Fixes**\n• **Instant Slot Detail Navigation**: Zero-latency screen transitions when opening slot details with dynamic player alias support.\n• **On-Demand Autocomplete**: Lazy loading for item and location autocomplete options to accelerate screen loads.\n• **Preferences UI Cleanup**: Streamlined notification preference screens.\n\nUpdate now on Google Play or download the latest APK from GitHub Releases!", "play_store": "", "github": "" } @@ -212,7 +257,7 @@ ] }, "release_notes": { - "discord": "**Archipelago Alerts Android App v1.6.18 Released!**\n\n**New Features**\n\u2022 **Push Notification Whitelist**: Want notifications for specific items (e.g. Progressive Swords, Bombs) even if filler/category mutes are enabled? You can now whitelist individual items or item groups per-game or globally!\n\n**Improvements & Fixes**\n\u2022 **Instant History Sync**: Refactored item history synchronization using cursor watermarks for faster load times and zero missing items.\n\u2022 **Item Index Tracking**: Received item ordering now tracks Archipelago's native item index for 100% item fidelity.\n\u2022 **Cheese Tracker Sync**: Improved slot claim validation and conflict resolution.\n\nUpdate now on Google Play or download the latest APK from GitHub Releases!", + "discord": "**Archipelago Alerts Android App v1.6.18 Released!**\n\n**New Features**\n• **Push Notification Whitelist**: Want notifications for specific items (e.g. Progressive Swords, Bombs) even if filler/category mutes are enabled? You can now whitelist individual items or item groups per-game or globally!\n\n**Improvements & Fixes**\n• **Instant History Sync**: Refactored item history synchronization using cursor watermarks for faster load times and zero missing items.\n• **Item Index Tracking**: Received item ordering now tracks Archipelago's native item index for 100% item fidelity.\n• **Cheese Tracker Sync**: Improved slot claim validation and conflict resolution.\n\nUpdate now on Google Play or download the latest APK from GitHub Releases!", "play_store": "", "github": "" } @@ -296,7 +341,7 @@ ] }, "release_notes": { - "discord": "**Archipelago Alerts Backend v1.6.22 Released!**\n\n**New: Cheese Tracker Notes & Statuses**\n\u2022 **Per-Slot State API**: `GET /api/user/tracked_slots` now includes a `cheese` object per slot (notes, progression/completion status, ping, last checked, ownership).\n\u2022 **Slot Editing**: New `PUT /rooms//slots//cheese` to edit notes/status/ping and refresh \"Last Checked\" (\"Still BK\"), with ownership checks and optimistic-conflict handling.\n\u2022 **Default Ping Preference**: `cheese_default_ping` is now applied at claim time, fixing the ping preference always defaulting to \"Never\".", + "discord": "**Archipelago Alerts Backend v1.6.22 Released!**\n\n**New: Cheese Tracker Notes & Statuses**\n• **Per-Slot State API**: `GET /api/user/tracked_slots` now includes a `cheese` object per slot (notes, progression/completion status, ping, last checked, ownership).\n• **Slot Editing**: New `PUT /rooms//slots//cheese` to edit notes/status/ping and refresh \"Last Checked\" (\"Still BK\"), with ownership checks and optimistic-conflict handling.\n• **Default Ping Preference**: `cheese_default_ping` is now applied at claim time, fixing the ping preference always defaulting to \"Never\".", "play_store": "", "github": "" } @@ -332,7 +377,7 @@ "fixes": [] }, "release_notes": { - "discord": "**Archipelago Alerts Backend v1.6.21 Released!**\n\n**Improvements & Fixes**\n\u2022 **Tracked Slot Item Count Payload**: Surfaced total item counts per slot in `GET /api/user/tracked_slots` to drive client-side progress calculation.\n\u2022 **Landing Page Version Syncing**: Fixed landing page version badges to resolve from `changelog.json` in production containers.", + "discord": "**Archipelago Alerts Backend v1.6.21 Released!**\n\n**Improvements & Fixes**\n• **Tracked Slot Item Count Payload**: Surfaced total item counts per slot in `GET /api/user/tracked_slots` to drive client-side progress calculation.\n• **Landing Page Version Syncing**: Fixed landing page version badges to resolve from `changelog.json` in production containers.", "play_store": "", "github": "" } @@ -350,7 +395,7 @@ }, { "title": "Cycle Jitter & Staggering", - "description": "Added per-cycle \u00b130s random jitter to the 5-minute poller sleep interval and expanded initial room stagger (1\u201360s) to prevent room polling tasks from re-synchronizing into waves over time." + "description": "Added per-cycle ±30s random jitter to the 5-minute poller sleep interval and expanded initial room stagger (1–60s) to prevent room polling tasks from re-synchronizing into waves over time." }, { "title": "SQLAlchemy Connection Pool Tuning", @@ -378,7 +423,7 @@ }, { "title": "Cycle Jitter & Staggering", - "description": "Added per-cycle \u00b130s random jitter to the 5-minute poller sleep interval and expanded initial room stagger (1\u201360s) to prevent room polling tasks from re-synchronizing into waves over time." + "description": "Added per-cycle ±30s random jitter to the 5-minute poller sleep interval and expanded initial room stagger (1–60s) to prevent room polling tasks from re-synchronizing into waves over time." }, { "title": "SQLAlchemy Connection Pool Tuning", @@ -401,7 +446,7 @@ ] }, "release_notes": { - "discord": "**Archipelago Alerts Backend v1.6.19 Released!**\n\n**Improvements & Fixes**\n\u2022 **Poller CPU & Resource Throttling**: Throttled concurrent room processing cycles to smooth CPU spikes.\n\u2022 **Cycle Jitter & Staggering**: Added random jitter to poller sleep intervals to prevent wave synchronization.\n\u2022 **SQLAlchemy Pool Tuning**: Optimized PostgreSQL connection pool size and recycling for high concurrency.\n\u2022 **Datapackage Cache Locking**: Prevented concurrent autocomplete requests from redundant datapackage fetches.", + "discord": "**Archipelago Alerts Backend v1.6.19 Released!**\n\n**Improvements & Fixes**\n• **Poller CPU & Resource Throttling**: Throttled concurrent room processing cycles to smooth CPU spikes.\n• **Cycle Jitter & Staggering**: Added random jitter to poller sleep intervals to prevent wave synchronization.\n• **SQLAlchemy Pool Tuning**: Optimized PostgreSQL connection pool size and recycling for high concurrency.\n• **Datapackage Cache Locking**: Prevented concurrent autocomplete requests from redundant datapackage fetches.", "play_store": "", "github": "" } diff --git a/backend/app/poller.py b/backend/app/poller.py index 5e8ab68..fc2f11f 100644 --- a/backend/app/poller.py +++ b/backend/app/poller.py @@ -8,7 +8,6 @@ import fnmatch import itertools import time -import re import ssl import certifi from dotenv import load_dotenv @@ -76,19 +75,6 @@ def chunked_iterable(iterable, size): # CORE HELPERS & SETUP # ============================================================================= -def get_app_version(): - try: - gradle_path = os.path.join(os.path.dirname(__file__), '../../android/app/build.gradle.kts') - if os.path.exists(gradle_path): - with open(gradle_path, 'r') as f: - content = f.read() - match = re.search(r'versionName\s*=\s*"([^"]+)"', content) - if match: - return match.group(1) - except Exception as e: - logging.warning(f"[VERSION] Could not read version from gradle: {e}") - return "1.0.0" - async def close_aiohttp_session(): session = getattr(thread_local_data, "aiohttp_session", None) if session: From 2d7030d1fb96739b5183691c365bbeb7a3fd546e Mon Sep 17 00:00:00 2001 From: wrjones104 Date: Tue, 4 Aug 2026 10:45:48 -0400 Subject: [PATCH 4/5] Add server v1.6.23 changelog entry for server-side ignore/whitelist fix Documents the backend half of 3c1b760 (already merged): filtering_service.py and the isIgnored/isWhitelisted fields on history/hint responses. Discord notes are intentionally left blank since the user-facing effect was already announced in the app v1.6.23 release notes; the GitHub notes cover the backend implementation for contributors. Co-Authored-By: Claude Sonnet 5 --- backend/CHANGELOG.md | 16 +++++++++++++++ backend/app/data/changelog.json | 36 +++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/backend/CHANGELOG.md b/backend/CHANGELOG.md index 9ddc707..522b70d 100644 --- a/backend/CHANGELOG.md +++ b/backend/CHANGELOG.md @@ -10,6 +10,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), > This file is generated from `backend/app/data/changelog.json`. +## [1.6.23] - 2026-08-03 + +_Server-Side Ignore/Whitelist Filtering_ + +> **GitHub Release Copy-Paste:** +> ```markdown +> ### Fixed +> - **Server-Side Ignore/Whitelist Computation**: Extracted ignore/whitelist matching (including checksum-scoped item-group resolution) into a shared `backend/app/services/filtering_service.py`, used by both the poller and the history/hint endpoints. History and hint endpoints now return `isIgnored`/`isWhitelisted` per item and hint, fixing item-group rules having no client-visible effect and hint filtering ignoring the game-specific scope of rules. +> ``` + +### Fixed +- **Item Group Filtering Computed Server-Side**: Item-group ignore/whitelist rules are now evaluated on the server and sent to the app directly, fixing group rules that previously had no effect in History. +- **Per-Game Hint Filtering Fix**: Hint filtering now correctly respects each rule's game scope instead of applying it across all your games. + +--- + ## [1.6.22] - 2026-08-03 _Cheese Tracker Notes & Statuses API_ diff --git a/backend/app/data/changelog.json b/backend/app/data/changelog.json index bce9ac5..42bf52a 100644 --- a/backend/app/data/changelog.json +++ b/backend/app/data/changelog.json @@ -292,6 +292,42 @@ } ], "server_releases": [ + { + "version": "1.6.23", + "component": "server", + "component_label": "Server", + "release_date": "2026-08-03", + "title": "Server-Side Ignore/Whitelist Filtering", + "highlights": [ + { + "title": "Item Group Filtering Computed Server-Side", + "description": "Item-group ignore/whitelist rules are now evaluated on the server and sent to the app directly, fixing group rules that previously had no effect in History." + }, + { + "title": "Per-Game Hint Filtering Fix", + "description": "Hint filtering now correctly respects each rule's game scope instead of applying it across all your games." + } + ], + "categories": { + "features": [], + "improvements": [], + "fixes": [ + { + "title": "Item Group Filtering Computed Server-Side", + "description": "Item-group ignore/whitelist rules are now evaluated on the server and sent to the app directly, fixing group rules that previously had no effect in History." + }, + { + "title": "Per-Game Hint Filtering Fix", + "description": "Hint filtering now correctly respects each rule's game scope instead of applying it across all your games." + } + ] + }, + "release_notes": { + "discord": "", + "play_store": "", + "github": "### Fixed\n- **Server-Side Ignore/Whitelist Computation**: Extracted ignore/whitelist matching (including checksum-scoped item-group resolution) into a shared `backend/app/services/filtering_service.py`, used by both the poller and the history/hint endpoints. History and hint endpoints now return `isIgnored`/`isWhitelisted` per item and hint, fixing item-group rules having no client-visible effect and hint filtering ignoring the game-specific scope of rules." + } + }, { "version": "1.6.22", "component": "server", From b49a095331725f088200fb70a73d2d0d47044c4b Mon Sep 17 00:00:00 2001 From: wrjones104 Date: Tue, 4 Aug 2026 10:45:55 -0400 Subject: [PATCH 5/5] Cap What's New list on the landing page, link to full changelog /api/whats_new grew unbounded as releases accumulate; add an optional ?limit=N param (backward compatible, no cap when omitted). The landing page now requests limit=5 and links to GitHub Releases for full history, which is already the project's de facto complete changelog (used for APK distribution since v1.6.10). Co-Authored-By: Claude Sonnet 5 --- backend/app/routes/whats_new_routes.py | 8 +++++++- backend/app/templates/index.html | 9 ++++++--- backend/tests/test_whats_new.py | 6 ++++++ 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/backend/app/routes/whats_new_routes.py b/backend/app/routes/whats_new_routes.py index 7fa0540..f111fde 100644 --- a/backend/app/routes/whats_new_routes.py +++ b/backend/app/routes/whats_new_routes.py @@ -16,10 +16,13 @@ def _load_changelog_data(): @whats_new_bp.route('/api/whats_new', methods=['GET']) def get_whats_new(): """ - GET /api/whats_new?target=app|server|all + GET /api/whats_new?target=app|server|all&limit=N Returns release notes and version info filtered by target (defaults to 'all'). + Optional `limit` caps the number of releases returned (newest first); omitted + or non-positive values return the full list. """ target = request.args.get('target', 'all').lower() + limit = request.args.get('limit', type=int) data = _load_changelog_data() if target == 'app': @@ -32,6 +35,9 @@ def get_whats_new(): releases = data.get("releases", []) latest_version = data.get("latest_version") + if limit is not None and limit > 0: + releases = releases[:limit] + return jsonify({ "status": "success", "target": target, diff --git a/backend/app/templates/index.html b/backend/app/templates/index.html index bf9c284..1761e49 100644 --- a/backend/app/templates/index.html +++ b/backend/app/templates/index.html @@ -279,10 +279,13 @@

What's New - @@ -387,8 +390,8 @@

menu.classList.toggle('hidden'); }); - // Fetch What's New Releases - fetch('/api/whats_new') + // Fetch What's New Releases (capped; older releases live on GitHub Releases) + fetch('/api/whats_new?limit=5') .then(res => res.json()) .then(data => { const container = document.getElementById('whats-new-container'); diff --git a/backend/tests/test_whats_new.py b/backend/tests/test_whats_new.py index 5316950..a295723 100644 --- a/backend/tests/test_whats_new.py +++ b/backend/tests/test_whats_new.py @@ -65,6 +65,12 @@ def test_get_whats_new_all(self): self.assertIn('releases', data) self.assertTrue(len(data['releases']) > 0) + def test_get_whats_new_limit(self): + response = self.client.get('/api/whats_new?limit=1') + self.assertEqual(response.status_code, 200) + data = response.get_json() + self.assertEqual(len(data['releases']), 1) + def test_get_whats_new_app_target(self): response = self.client.get('/api/whats_new?target=app') self.assertEqual(response.status_code, 200)