Release: v1.0.0 Stable with Alpha/Beta Channels - #8
Conversation
- Fix Android codebase issues (Issue #5) - Remove obsolete SDK warnings - Fix unused imports - Add KDoc documentation to public APIs - Fix Compose state allocation warnings - Implement alpha/beta/stable release channels (Issue #7) - Add ReleaseChannel enum (ALPHA, BETA, STABLE) - Update UpdateChecker to filter releases by channel - Add channel selection in Settings - Tag patterns: v{version}, v{version}-beta, v{version}-alpha - Update GitHub release workflow for channels - Support alpha/beta/stable channels - Dynamic tagging and pre-release flags All changes ready for v1.0.0 stable release. Co-authored-by: Hermes Agent <claude@anthropic.com>
📝 WalkthroughWalkthroughThe Android app adds Stable, Beta, Alpha, and Nightly update channels. It persists channel preferences and update metadata, provides channel selection UI, adds KDoc to public components, removes unused imports, and uses primitive-specific Compose state helpers. ChangesRelease channels and update engine
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant UpdateSettingsPage
participant SettingsManager
participant UpdateChecker
participant GitHub
User->>UpdateSettingsPage: Select release channel
UpdateSettingsPage->>SettingsManager: Persist channel and update state
User->>UpdateSettingsPage: Start update check
UpdateSettingsPage->>UpdateChecker: Fetch latest release for channel
UpdateChecker->>GitHub: Request release metadata
GitHub-->>UpdateChecker: Return release data
UpdateChecker-->>UpdateSettingsPage: Return ReleaseInfo or null
UpdateSettingsPage->>SettingsManager: Persist checked version
Suggested reviewers: 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches 💡 2📝 Generate docstrings 💡
🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 9
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@mobile/app/src/main/java/com/bytecats/metanoia/bible/BibleDatabase.kt`:
- Line 22: Update the database location KDoc near dbFile to describe the path as
context.filesDir/bible.db in app-private storage, matching the
File(context.filesDir, "bible.db") construction.
In `@mobile/app/src/main/java/com/bytecats/metanoia/bible/DeepLink.kt`:
- Line 55: Update the `@param` uri KDoc for DeepLink.parse to document all schemes
accepted by DeepLink.parseParts, including http, https, and metanoia; preserve
the existing parsing behavior.
In `@mobile/app/src/main/java/com/bytecats/metanoia/settings/SettingsManager.kt`:
- Around line 145-147: Update the releaseChannel getter to detect when the
"release_channel" preference is absent and, only in that case, read the legacy
"nightly_updates_enabled" flag; return and persist ReleaseChannel.NIGHTLY when
the flag is true, otherwise preserve the existing default and parsing behavior.
Keep the setter unchanged.
- Around line 149-152: Update MainViewModel.performAutoUpdate and
startAutoUpdateLoop to check SettingsManager.updateSettings.updatesEnabled
before starting the loop and before each continued iteration. Replace the
deprecated channel-agnostic fetchLatest() call with channel-aware update
checking via fetchLatestForChannel(releaseChannel), preserving the selected
stable, beta, or alpha channel.
In
`@mobile/app/src/main/java/com/bytecats/metanoia/ui/effects/core/GraphicsQualityManager.kt`:
- Line 439: Update supportsComputeShaders() to determine compute-shader support
through an actual GPU/OpenGL capability probe instead of unconditionally
returning true for minSdkVersion 28+. Assign
DeviceCapabilities.supportsComputeShaders from the probe result, preserving the
existing capability flow so compute-based rendering is selected only when the
device genuinely supports it.
In
`@mobile/app/src/main/java/com/bytecats/metanoia/ui/screens/settings/UpdateSettingsPage.kt`:
- Around line 224-249: The channel description selection in the when expression
infers List<Any>; replace the destructured list values with a typed
ChannelDescription data class containing title, description, icon, and color,
then access those typed properties in the surrounding UI code. Preserve the
existing values for each ReleaseChannel.
- Around line 71-80: Update the onOptionSelected handler in UpdateSettingsPage
so changing releaseChannel also clears viewModel.availableUpdate before
resetting hasChecked. Preserve the existing nightly-channel update enabling
behavior and ensure no release from the previous channel remains available for
display or installation.
In `@mobile/app/src/main/java/com/bytecats/metanoia/update/UpdateChecker.kt`:
- Around line 122-134: Update fetchAllReleases to follow GitHub pagination by
reading the successful response’s Link header for rel="next", fetching each
subsequent URL, and aggregating parsed releases. Stop as soon as the requested
channel release is found or when no next link remains, while preserving
empty-list handling for unsuccessful responses or missing bodies.
In `@mobile/app/src/main/java/com/bytecats/metanoia/viewmodel/MainViewModel.kt`:
- Line 50: Update the KDoc for MainViewModel’s constructor argument from
`@property` application to `@param` application, documenting the application
parameter without implying that MainViewModel owns an application property.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 10bf4f6d-d181-4a68-8bf6-64ed224fc38d
📒 Files selected for processing (16)
mobile/app/src/main/java/com/bytecats/metanoia/MainActivity.ktmobile/app/src/main/java/com/bytecats/metanoia/bible/BibleDatabase.ktmobile/app/src/main/java/com/bytecats/metanoia/bible/BibleGatewayScraper.ktmobile/app/src/main/java/com/bytecats/metanoia/bible/BibleManager.ktmobile/app/src/main/java/com/bytecats/metanoia/bible/ChapterScraper.ktmobile/app/src/main/java/com/bytecats/metanoia/bible/DeepLink.ktmobile/app/src/main/java/com/bytecats/metanoia/settings/SettingsManager.ktmobile/app/src/main/java/com/bytecats/metanoia/tts/TTSAudioPlayer.ktmobile/app/src/main/java/com/bytecats/metanoia/ui/effects/core/GraphicsQualityManager.ktmobile/app/src/main/java/com/bytecats/metanoia/ui/screens/BibleScreen.ktmobile/app/src/main/java/com/bytecats/metanoia/ui/screens/CollectionScreen.ktmobile/app/src/main/java/com/bytecats/metanoia/ui/screens/settings/ReaderSettingsPage.ktmobile/app/src/main/java/com/bytecats/metanoia/ui/screens/settings/SettingsComponents.ktmobile/app/src/main/java/com/bytecats/metanoia/ui/screens/settings/UpdateSettingsPage.ktmobile/app/src/main/java/com/bytecats/metanoia/update/UpdateChecker.ktmobile/app/src/main/java/com/bytecats/metanoia/viewmodel/MainViewModel.kt
💤 Files with no reviewable changes (2)
- mobile/app/src/main/java/com/bytecats/metanoia/bible/ChapterScraper.kt
- mobile/app/src/main/java/com/bytecats/metanoia/bible/BibleGatewayScraper.kt
| * - [InterlinearDao]: Interlinear word-by-word translations | ||
| * - [LexiconDao]: Strong's lexicon definitions | ||
| * | ||
| * Database location: /data/data/com.bytecats.metanoia/files/bible.db |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Files matching BibleDatabase.kt:"
fd -a 'BibleDatabase\.kt$' . || true
file="$(fd 'BibleDatabase\.kt$' . | head -n1 || true)"
if [ -n "${file:-}" ]; then
echo
echo "Line count:"
wc -l "$file"
echo
echo "Relevant contents:"
cat -n "$file" | sed -n '1,80p'
fi
echo
echo "Search for dbFile/filesDir/bible.db documents:"
rg -n "dbFile|filesDir|bible\.db|/data/data" . -g '!build' -g '!node_modules' -g '!dist' || trueRepository: 4cecoder/metanoia
Length of output: 18194
Document the database path from context.filesDir.
dbFile is constructed with File(context.filesDir, "bible.db"), so the /data/data/... KDoc path does not describe the actual API contract. Describe the location as context.filesDir/bible.db in app-private storage.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@mobile/app/src/main/java/com/bytecats/metanoia/bible/BibleDatabase.kt` at
line 22, Update the database location KDoc near dbFile to describe the path as
context.filesDir/bible.db in app-private storage, matching the
File(context.filesDir, "bible.db") construction.
| /** | ||
| * Parse a deep-link URI into a VerseReference. | ||
| * | ||
| * @param uri The Android Uri to parse (either metanoia:// or https:// scheme) |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Keep parse documentation aligned with accepted schemes.
DeepLink.parseParts accepts http, https, and metanoia, but the parse KDoc documents only metanoia and https. If http is supported, include it in @param uri. Otherwise, reject it in parseParts.
Proposed documentation fix
- * `@param` uri The Android Uri to parse (either metanoia:// or https:// scheme)
+ * `@param` uri The Android Uri to parse (metanoia://, http://, or https:// scheme)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| * @param uri The Android Uri to parse (either metanoia:// or https:// scheme) | |
| * `@param` uri The Android Uri to parse (metanoia://, http://, or https:// scheme) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@mobile/app/src/main/java/com/bytecats/metanoia/bible/DeepLink.kt` at line 55,
Update the `@param` uri KDoc for DeepLink.parse to document all schemes accepted
by DeepLink.parseParts, including http, https, and metanoia; preserve the
existing parsing behavior.
| var releaseChannel: ReleaseChannel | ||
| get() = ReleaseChannel.fromString(prefs.getString("release_channel", ReleaseChannel.STABLE.name)) | ||
| set(value) = prefs.edit().putString("release_channel", value.name).apply() |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Migrate the legacy nightly preference when the new key is absent.
Existing installations can contain nightly_updates_enabled=true without release_channel. This getter then returns STABLE, so the user loses the prior nightly selection. Read the legacy flag only when release_channel is absent, then persist the migrated NIGHTLY value.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@mobile/app/src/main/java/com/bytecats/metanoia/settings/SettingsManager.kt`
around lines 145 - 147, Update the releaseChannel getter to detect when the
"release_channel" preference is absent and, only in that case, read the legacy
"nightly_updates_enabled" flag; return and persist ReleaseChannel.NIGHTLY when
the flag is true, otherwise preserve the existing default and parsing behavior.
Keep the setter unchanged.
| /** Enable/disable automatic update checking for the selected channel */ | ||
| var updatesEnabled: Boolean | ||
| get() = prefs.getBoolean("updates_enabled", true) | ||
| set(value) = prefs.edit().putBoolean("updates_enabled", value).apply() |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
ast-grep outline mobile/app/src/main/java/com/bytecats/metanoia/viewmodel/MainViewModel.kt \
--items all --view expanded
rg -n -C 5 \
'performAutoUpdate|fetchLatestForChannel\s*\(|fetchLatest\s*\(|availableUpdate|releaseChannel|updatesEnabled' \
mobile/app/src/main/java/com/bytecats/metanoiaRepository: 4cecoder/metanoia
Length of output: 33848
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '80,125p' mobile/app/src/main/java/com/bytecats/metanoia/update/UpdateChecker.kt
sed -n '330,390p' mobile/app/src/main/java/com/bytecats/metanoia/update/UpdateChecker.kt
rg -n -C 3 'class ReleaseChannel|enum class ReleaseChannel|releaseChannel|updatesEnabled|startAutoUpdateLoop' mobile/app/src/main/java/com/bytecats/metanoiaRepository: 4cecoder/metanoia
Length of output: 17185
Make the auto-update loop honor updateSettings.
MainViewModel.performAutoUpdate() calls the deprecated channel-agnostic fetchLatest(), while the Settings UI uses fetchLatestForChannel(releaseChannel) and reads updatesEnabled. Add updatesEnabled checks before starting and continuing startAutoUpdateLoop(), and replace the loop fetch with channel-aware checks to keep stable/beta/alpha installs from pulling nightly updates.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@mobile/app/src/main/java/com/bytecats/metanoia/settings/SettingsManager.kt`
around lines 149 - 152, Update MainViewModel.performAutoUpdate and
startAutoUpdateLoop to check SettingsManager.updateSettings.updatesEnabled
before starting the loop and before each continued iteration. Replace the
deprecated channel-agnostic fetchLatest() call with channel-aware update
checking via fetchLatestForChannel(releaseChannel), preserving the selected
stable, beta, or alpha channel.
| */ | ||
| private fun supportsComputeShaders(): Boolean { | ||
| return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N | ||
| return true // Always true for minSdkVersion 28+ |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Trace capability consumers and runtime graphics probes.
rg -n -C 5 '\bsupportsComputeShaders\b|GL_COMPUTE_SHADER|GLES31|compute[[:space:]-]?shader|computeShader' mobile/app/src/main/java
rg -n -C 3 'minSdk|compileSdk|targetSdk' mobileRepository: 4cecoder/metanoia
Length of output: 7454
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== candidate files containing DeviceCapabilities/reference =="
rg -n -C 4 'DeviceCapabilities|int\.kotlin|deviceCapabilities|graphicsCapabilities|supportsComputeShaders|\bsupportsComputeShaders\b' mobile/app/src/main/java mobile/app/src/main/kotlin mobile -g '!**/README.md' || true
echo
echo "== GraphicsQualityManager relevant sections =="
wc -l mobile/app/src/main/java/com/bytecats/metanoia/ui/effects/core/GraphicsQualityManager.kt
sed -n '1,130p' mobile/app/src/main/java/com/bytecats/metanoia/ui/effects/core/GraphicsQualityManager.kt
sed -n '300,470p' mobile/app/src/main/java/com/bytecats/metanoia/ui/effects/core/GraphicsQualityManager.kt
sed -n '600,660p' mobile/app/src/main/java/com/bytecats/metanoia/ui/effects/core/GraphicsQualityManager.ktRepository: 4cecoder/metanoia
Length of output: 40764
Probe compute-shader support instead of always returning true.
minSdkVersion 28 does not guarantee compute-shader capability. supportsComputeShaders() currently reports every device as supported via DeviceCapabilities.supportsComputeShaders, so later graphics paths may select compute-based rendering on devices that do not support it. Base this value on an actual GPU/GL probe before assigning it.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@mobile/app/src/main/java/com/bytecats/metanoia/ui/effects/core/GraphicsQualityManager.kt`
at line 439, Update supportsComputeShaders() to determine compute-shader support
through an actual GPU/OpenGL capability probe instead of unconditionally
returning true for minSdkVersion 28+. Assign
DeviceCapabilities.supportsComputeShaders from the probe result, preserving the
existing capability flow so compute-based rendering is selected only when the
device genuinely supports it.
| onOptionSelected = { | ||
| releaseChannel = it | ||
| settings.releaseChannel = it | ||
| // If switching to nightly, enable updates automatically | ||
| if (it == ReleaseChannel.NIGHTLY) { | ||
| updatesEnabled = true | ||
| settings.updatesEnabled = true | ||
| } | ||
| hasChecked = false // Reset check state when channel changes | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Clear the selected update when the channel changes.
hasChecked resets, but viewModel.availableUpdate retains the release from the previous channel. The page then shows and installs that stale release after the user selects a different channel. Clear the pending update when releaseChannel changes.
Proposed fix
onOptionSelected = {
+ if (it != releaseChannel) {
+ viewModel.availableUpdate.value = null
+ downloadFailed = false
+ hasChecked = false
+ }
releaseChannel = it
settings.releaseChannel = it
- hasChecked = false
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| onOptionSelected = { | |
| releaseChannel = it | |
| settings.releaseChannel = it | |
| // If switching to nightly, enable updates automatically | |
| if (it == ReleaseChannel.NIGHTLY) { | |
| updatesEnabled = true | |
| settings.updatesEnabled = true | |
| } | |
| hasChecked = false // Reset check state when channel changes | |
| } | |
| onOptionSelected = { | |
| if (it != releaseChannel) { | |
| viewModel.availableUpdate.value = null | |
| downloadFailed = false | |
| hasChecked = false | |
| } | |
| releaseChannel = it | |
| settings.releaseChannel = it | |
| // If switching to nightly, enable updates automatically | |
| if (it == ReleaseChannel.NIGHTLY) { | |
| updatesEnabled = true | |
| settings.updatesEnabled = true | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@mobile/app/src/main/java/com/bytecats/metanoia/ui/screens/settings/UpdateSettingsPage.kt`
around lines 71 - 80, Update the onOptionSelected handler in UpdateSettingsPage
so changing releaseChannel also clears viewModel.availableUpdate before
resetting hasChecked. Preserve the existing nightly-channel update enabling
behavior and ensure no release from the previous channel remains available for
display or installation.
| val (title, description, icon, color) = when (channel) { | ||
| ReleaseChannel.STABLE -> listOf( | ||
| "Stable Channel", | ||
| "Production-ready releases. Thoroughly tested and recommended for most users.", | ||
| Icons.Default.CheckCircle, | ||
| MaterialTheme.colorScheme.primary | ||
| ) | ||
| ReleaseChannel.BETA -> listOf( | ||
| "Beta Channel", | ||
| "Testing releases with new features. May have bugs but receives regular testing.", | ||
| Icons.Default.Science, | ||
| MaterialTheme.colorScheme.tertiary | ||
| ) | ||
| ReleaseChannel.ALPHA -> listOf( | ||
| "Alpha Channel", | ||
| "Early builds with the latest changes. May be unstable and is for developers only.", | ||
| Icons.Default.BugReport, | ||
| MaterialTheme.colorScheme.error | ||
| ) | ||
| ReleaseChannel.NIGHTLY -> listOf( | ||
| "Nightly Channel", | ||
| "Latest master builds from the rolling \"latest\" tag. May be very unstable.", | ||
| Icons.Default.Flare, | ||
| MaterialTheme.colorScheme.secondary | ||
| ) | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n -C 6 \
'private fun ChannelDescriptionCard|val \(title, description, icon, color\)|listOf\(' \
mobile/app/src/main/java/com/bytecats/metanoia/ui/screens/settings/UpdateSettingsPage.ktRepository: 4cecoder/metanoia
Length of output: 1485
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '222,280p' mobile/app/src/main/java/com/bytecats/metanoia/ui/screens/settings/UpdateSettingsPage.ktRepository: 4cecoder/metanoia
Length of output: 2341
Use a typed channel description object.
listOf(String, String, ImageVector, Color) infers List<Any>, so Text(title), Icon(icon, ...), and color.copy(...) do not type-check. Replace these lists with a ChannelDescription data class.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@mobile/app/src/main/java/com/bytecats/metanoia/ui/screens/settings/UpdateSettingsPage.kt`
around lines 224 - 249, The channel description selection in the when expression
infers List<Any>; replace the destructured list values with a typed
ChannelDescription data class containing title, description, icon, and color,
then access those typed properties in the surrounding UI code. Preserve the
existing values for each ReleaseChannel.
| private suspend fun fetchAllReleases(client: OkHttpClient): List<ReleaseInfo> = | ||
| withContext(Dispatchers.IO) { | ||
| try { | ||
| val req = Request.Builder() | ||
| .url(ALL_RELEASES_API_URL) | ||
| .header("Accept", "application/vnd.github+json") | ||
| .get() | ||
| .build() | ||
| client.newCall(req).execute().use { resp -> | ||
| if (!resp.isSuccessful) return@withContext emptyList() | ||
| val body = resp.body?.string() ?: return@withContext emptyList() | ||
| parseAllReleases(body) | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
api='https://api.github.com/repos/4cecoder/metanoia/releases'
headers="$(mktemp)"
body="$(mktemp)"
trap 'rm -f "$headers" "$body"' EXIT
curl -fsS -D "$headers" -o "$body" \
-H 'Accept: application/vnd.github+json' \
"$api"
echo 'First-page release count:'
jq 'length' "$body"
echo 'Pagination header:'
grep -i '^link:' "$headers" || true
echo 'First-page tags:'
jq -r '.[].tag_name' "$body"Repository: 4cecoder/metanoia
Length of output: 206
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo 'Files matching UpdateChecker:'
fd -a 'UpdateChecker\.kt$' . || true
echo 'Relevant symbols/usages:'
rg -n "UpdateChecker|fetchAllReleases|parseAllReleases|ALL_RELEASES_API_URL|ReleaseInfo|selected|channel|findLatest|release" mobile/app/src/main/java/com/bytecats/metanoia/update || true
echo 'Inspect UpdateChecker outline/content:'
if [ -f mobile/app/src/main/java/com/bytecats/metanoia/update/UpdateChecker.kt ]; then
wc -l mobile/app/src/main/java/com/bytecats/metanoia/update/UpdateChecker.kt
cat -n mobile/app/src/main/java/com/bytecats/metanoia/update/UpdateChecker.kt
fiRepository: 4cecoder/metanoia
Length of output: 25310
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
# Check whether the current repository has enough release-tag metadata locally
# to prove the repository can exceed a single GitHub releases page.
import json, pathlib, re
repo = pathlib.Path('.')
tags = []
for path in repo.glob('.git/refs/tags/**'):
if path.is_file():
with open(path) as f:
content = f.read().strip()
match = re.fullmatch(r'^[0-9a-fA-F]{40}\s+(?:ref\s+)?refs/tags/(.+)$', content)
if match:
tags.append(match.group(1))
print("tracked refs/tags count:", len(tags))
channel_matches = {ch.value: [] for ch in ["stable", "beta", "alpha", "nightly"]}
for tag in tags:
lower = tag.lower()
# Stable only if no channel suffix; alpha/beta/nightly only if channel suffix is first matching suffix.
if "-beta" in lower:
channel_matches["beta"].append(tag)
elif "-alpha" in lower:
channel_matches["alpha"].append(tag)
elif "-nightly" in lower:
channel_matches["nightly"].append(tag)
else:
channel_matches["stable"].append(tag)
for ch, vals in channel_matches.items():
print(f"{ch} matching tags: {len(vals)}")
if vals:
print(f" first/last sample: {vals[0]}, ..., {vals[-1]}")
PYRepository: 4cecoder/metanoia
Length of output: 351
🌐 Web query:
GitHub Releases API list releases default per_page 30 return first 30 releases Link header pagination
💡 Result:
When using the GitHub Releases API to list releases, the default value for the per_page parameter is 30 [1][2][3]. This means that a request without a specified per_page parameter will return the first 30 releases [1][4]. You can adjust this number using the per_page query parameter, which accepts a maximum value of 100 [1][5]. For pagination, the GitHub REST API utilizes the Link header in the response to help you navigate through results [6][7]. This header includes URLs for the first, last, next, and previous pages, indicated by the rel attribute (e.g., rel="next", rel="last") [6][7]. If an endpoint does not support pagination, or if all available results fit on a single page, the Link header will be omitted from the response [6][7].
Citations:
- 1: https://docs.github.com/en/rest/releases/releases
- 2: https://docs.github.com/en/rest/releases/releases?apiVersion=2026-03-10&apiversion=2022-11-28%2F1000
- 3: https://docs.github.com/v3/repos/releases
- 4: https://docs.github.com/en/enterprise-server@3.17/rest/releases/releases
- 5: https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.2022-11-28.yaml
- 6: https://docs.github.com/en/rest/using-the-rest-api/using-pagination-in-the-rest-api?apiVersion=2026-03-10
- 7: https://docs.github.com/rest/using-the-rest-api/using-pagination-in-the-rest-api
🌐 Web query:
GitHub API "Accept" header version "application/vnd.github+json"
💡 Result:
The application/vnd.github+json media type is the standard value used in the Accept header for GitHub REST API requests [1][2]. When making requests to the GitHub REST API, you should include this header to ensure the API returns data in the expected JSON format [1]. While application/vnd.github+json is the recommended default, it is frequently used alongside the X-GitHub-Api-Version header, which specifies the API version (e.g., X-GitHub-Api-Version: 2022-11-28) to ensure consistent behavior across API updates [1][2]. Example of a standard request using curl: curl --request GET \ --url "https://api.github.com/octocat" \ --header "Accept: application/vnd.github+json" \ --header "X-GitHub-Api-Version: 2022-11-28" [2]
Citations:
- 1: https://docs.github.com/rest/guides/getting-started-with-the-rest-api
- 2: https://docs.github.com/en/rest/overview/api-previews/
🌐 Web query:
GitHub REST API releases endpoint list repository releases GitHub API accept application/vnd.github+json only
💡 Result:
To list repository releases using the GitHub REST API, you should use the endpoint GET /repos/{owner}/{repo}/releases [1][2]. Regarding the use of application/vnd.github+json, it is the recommended standard media type for nearly all GitHub REST API requests [3][4]. However, GitHub's versioning strategy has moved away from embedding versions within the media type [4]. Instead, you should specify the API version using the X-GitHub-Api-Version header [5][6][4]. Key points for your request: 1. Endpoint: GET /repos/{owner}/{repo}/releases [1][2]. 2. Accept Header: Use Accept: application/vnd.github+json [3][4]. 3. Version Header: You should explicitly specify the API version (e.g., X-GitHub-Api-Version: 2022-11-28) to ensure stability [5][4]. If this header is omitted, the API defaults to the 2022-11-28 version [5][4]. Example curl command: curl -L \ -H "Accept: application/vnd.github+json" \ -H "X-GitHub-Api-Version: 2022-11-28" \ https://api.github.com/repos/OWNER/REPO/releases
Citations:
- 1: https://docs.github.com/en/rest/releases/releases
- 2: https://docs.github.com/v3/repos/releases
- 3: https://docs.github.com/rest/guides/getting-started-with-the-rest-api
- 4: https://bryanwhiting.com/ai/decoding-githubs-media-types-applicationvndgithubj/
- 5: https://docs.github.com/en/rest/about-the-rest-api/api-versions
- 6: https://github.blog/developer-skills/github/to-infinity-and-beyond-enabling-the-future-of-githubs-rest-api-with-api-versioning/
Handle GitHub Releases pagination in fetchAllReleases.
The releases endpoint returns the first page by default and uses the Link header to provide subsequent pages. Parse rel="next" from successful responses and fetch pages until the requested channel release is found or no next page remains.
🧰 Tools
🪛 ast-grep (0.45.0)
[error] 124-125: An outbound HTTP request is built from a non-literal URL: URL(...).openConnection(), OkHttp's Request.Builder().url(...), or a Spring RestTemplate call (getForObject/getForEntity/postForObject/postForEntity/exchange/execute). When the URL is derived from a variable, method call, concatenation, or interpolation, an attacker can point the request at internal services, cloud metadata endpoints (e.g. 169.254.169.254), or arbitrary hosts (Server-Side Request Forgery). Do not build request targets from untrusted input: validate the URL against a strict allowlist of permitted hosts/schemes, resolve and re-check the host after DNS resolution, and reject internal/link-local/loopback addresses before connecting.
Context: Request.Builder()
.url(ALL_RELEASES_API_URL)
Note: [CWE-918] Server-Side Request Forgery (SSRF).
(ssrf-outbound-request-tainted-url-kotlin)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@mobile/app/src/main/java/com/bytecats/metanoia/update/UpdateChecker.kt`
around lines 122 - 134, Update fetchAllReleases to follow GitHub pagination by
reading the successful response’s Link header for rel="next", fetching each
subsequent URL, and aggregating parsed releases. Stop as soon as the requested
channel release is found or when no next link remains, while preserving
empty-list handling for unsuccessful responses or missing bodies.
| * The ViewModel uses [AndroidViewModel] to safely access the application context | ||
| * without memory leaks (applicationContext is tied to application lifecycle, not activities). | ||
| * | ||
| * @property application The application context for accessing system services |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
file="mobile/app/src/main/java/com/bytecats/metanoia/viewmodel/MainViewModel.kt"
if [ -f "$file" ]; then
echo "== file exists =="
wc -l "$file"
echo
echo "== relevant header/destructor lines =="
sed -n '1,80p' "$file" | cat -n
else
echo "File not found: $file"
echo "Candidate files:"
fd -a 'MainViewModel\.kt$' .
fi
echo
echo "== Kotlin/docs lint check availability =="
command -v kotlinc || true
command -v dokka || trueRepository: 4cecoder/metanoia
Length of output: 4031
Use @param for the constructor argument.
MainViewModel(application: Application) does not declare a application property or primary-parameter KDoc in the class, so @property application documents a member that this constructor does not own. Use @param application for this constructor argument.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@mobile/app/src/main/java/com/bytecats/metanoia/viewmodel/MainViewModel.kt` at
line 50, Update the KDoc for MainViewModel’s constructor argument from `@property`
application to `@param` application, documenting the application parameter without
implying that MainViewModel owns an application property.
v1.0.0 Stable Release
This PR prepares the Android app for v1.0.0 stable release with alpha/beta channel support.
What's Included
Issue #5: Android Codebase Cleanup ✅
Issue #7: Alpha/Beta Release Channels ✅
v{version},v{version}-beta,v{version}-alphaGitHub Workflow Update ✅
release-latest.ymlfor channel supportRelease Channels
Testing Checklist
Breaking Changes
None. All changes are backward compatible.
Deprecations
feature/gl-shader-effectsbranchRelated Issues
Next Steps
After merging this PR:
Summary by CodeRabbit
New Features
Bug Fixes
Documentation