Prepare Mora v0.3.2 for signed release - #14
Conversation
|
Warning Review limit reached
Next review available in: 49 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughVersion 0.3.2 updates revision-aware document saving, release build validation, signing and publication procedures, application visuals, issue intake, and product documentation. Changesv0.3.2 application behavior
Release engineering
Product and repository documentation
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant Editor
participant MoraApp
participant MarkdownViewModel
participant DocumentRepository
Editor->>MoraApp: select Save
MoraApp->>MarkdownViewModel: start save
MarkdownViewModel->>DocumentRepository: write revisioned snapshot
DocumentRepository-->>MarkdownViewModel: return write result
MarkdownViewModel-->>Editor: update saving and dirty indicators
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 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: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
app/src/main/java/de/unbow/mora/ui/MoraApp.kt (1)
290-297: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winBind the Save As result to the launching document session.
The callback calls
saveAsagainst whichever document is current when it returns. If the document session changes while the picker is open, an old picker result can write the newer document to—and rebind it to—the old destination. Capture the launchingsessionIdand ignore results unless it still matches before persisting permission or callingsaveAs.Proposed fix
- var saveAsPending by rememberSaveable(state.sessionId) { mutableStateOf(false) } + var saveAsRequestSessionId by rememberSaveable { mutableStateOf<Long?>(null) } + val saveAsPending = saveAsRequestSessionId == state.sessionId val createDocument = rememberLauncherForActivityResult( contract = ActivityResultContracts.CreateDocument("text/markdown"), ) { uri -> - saveAsPending = false - uri ?: return@rememberLauncherForActivityResult + val requestSessionId = saveAsRequestSessionId + saveAsRequestSessionId = null + if (uri == null || requestSessionId != markdownViewModel.uiState.sessionId) { + return@rememberLauncherForActivityResult + } DocumentRepository.persistPermission(context, uri) markdownViewModel.saveAs(context, uri, notifySave) } - saveAsPending = true + saveAsRequestSessionId = state.sessionId createDocument.launch(Also applies to: 494-510
🤖 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 `@app/src/main/java/de/unbow/mora/ui/MoraApp.kt` around lines 290 - 297, Update the CreateDocument launcher around createDocument to capture the current document sessionId when launching Save As, then in its callback ignore results whose sessionId no longer matches. Perform the session check before DocumentRepository.persistPermission and markdownViewModel.saveAs, while preserving saveAsPending cleanup and null-URI handling.
🤖 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 @.github/workflows/android.yml:
- Around line 55-60: Update the RELEASE_MAPPING artifact path in
.github/workflows/android.yml (lines 55-60) and .github/workflows/release.yml
(lines 218-224) to use AGP’s actual release mapping filename, mapping.txt,
instead of mapping.prt; keep all checks and upload steps referencing the
corrected RELEASE_MAPPING value.
In `@app/src/main/java/de/unbow/mora/model/MarkdownViewModel.kt`:
- Around line 383-385: Update the failure branch in the save flow around
stateAfterSaveFailure and DocumentSaveResult.Failed to recompute isDirty after
isSaving is cleared, comparing the current content with persistedContent so
edits reverted during the active save are marked clean.
---
Outside diff comments:
In `@app/src/main/java/de/unbow/mora/ui/MoraApp.kt`:
- Around line 290-297: Update the CreateDocument launcher around createDocument
to capture the current document sessionId when launching Save As, then in its
callback ignore results whose sessionId no longer matches. Perform the session
check before DocumentRepository.persistPermission and markdownViewModel.saveAs,
while preserving saveAsPending cleanup and null-URI handling.
🪄 Autofix (Beta)
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: 02eb2559-418c-4da5-85bd-5f43ddffca2b
⛔ Files ignored due to path filters (26)
app/src/main/res/mipmap-hdpi/ic_launcher.pngis excluded by!**/*.pngapp/src/main/res/mipmap-hdpi/ic_launcher_night.pngis excluded by!**/*.pngapp/src/main/res/mipmap-hdpi/ic_launcher_pine.pngis excluded by!**/*.pngapp/src/main/res/mipmap-hdpi/ic_launcher_round.pngis excluded by!**/*.pngapp/src/main/res/mipmap-mdpi/ic_launcher.pngis excluded by!**/*.pngapp/src/main/res/mipmap-mdpi/ic_launcher_night.pngis excluded by!**/*.pngapp/src/main/res/mipmap-mdpi/ic_launcher_pine.pngis excluded by!**/*.pngapp/src/main/res/mipmap-mdpi/ic_launcher_round.pngis excluded by!**/*.pngapp/src/main/res/mipmap-xhdpi/ic_launcher.pngis excluded by!**/*.pngapp/src/main/res/mipmap-xhdpi/ic_launcher_night.pngis excluded by!**/*.pngapp/src/main/res/mipmap-xhdpi/ic_launcher_pine.pngis excluded by!**/*.pngapp/src/main/res/mipmap-xhdpi/ic_launcher_round.pngis excluded by!**/*.pngapp/src/main/res/mipmap-xxhdpi/ic_launcher.pngis excluded by!**/*.pngapp/src/main/res/mipmap-xxhdpi/ic_launcher_night.pngis excluded by!**/*.pngapp/src/main/res/mipmap-xxhdpi/ic_launcher_pine.pngis excluded by!**/*.pngapp/src/main/res/mipmap-xxhdpi/ic_launcher_round.pngis excluded by!**/*.pngapp/src/main/res/mipmap-xxxhdpi/ic_launcher.pngis excluded by!**/*.pngapp/src/main/res/mipmap-xxxhdpi/ic_launcher_night.pngis excluded by!**/*.pngapp/src/main/res/mipmap-xxxhdpi/ic_launcher_pine.pngis excluded by!**/*.pngapp/src/main/res/mipmap-xxxhdpi/ic_launcher_round.pngis excluded by!**/*.pngdocs/assets/social-preview.pngis excluded by!**/*.pngdocs/screenshots/v0.3.2/editor-or-settings.pngis excluded by!**/*.pngdocs/screenshots/v0.3.2/home.pngis excluded by!**/*.pngdocs/screenshots/v0.3.2/launcher.pngis excluded by!**/*.pngdocs/screenshots/v0.3.2/reader.pngis excluded by!**/*.pngdocs/screenshots/v0.3.2/toc-or-search.pngis excluded by!**/*.png
📒 Files selected for processing (27)
.github/ISSUE_TEMPLATE/bug_report.yml.github/ISSUE_TEMPLATE/config.yml.github/ISSUE_TEMPLATE/feature_request.yml.github/workflows/android.yml.github/workflows/release.ymlCHANGELOG.mdPRIVACY.mdPROJECT_STATUS.mdREADME.mdREADME.zh-CN.mdROADMAP.mdSECURITY.mdapp/build.gradle.ktsapp/proguard-rules.proapp/src/main/java/de/unbow/mora/model/MarkdownViewModel.ktapp/src/main/java/de/unbow/mora/ui/AppearanceSheet.ktapp/src/main/java/de/unbow/mora/ui/DocumentScreen.ktapp/src/main/java/de/unbow/mora/ui/MoraApp.ktapp/src/main/res/drawable/ic_launcher_foreground.xmlapp/src/main/res/drawable/ic_launcher_foreground_night.xmlapp/src/main/res/drawable/ic_launcher_foreground_pine.xmlapp/src/main/res/drawable/ic_launcher_monochrome.xmlapp/src/main/res/values-zh-rCN/strings.xmlapp/src/main/res/values/colors.xmlapp/src/main/res/values/strings.xmlapp/src/test/java/de/unbow/mora/model/MarkdownViewModelSaveCoordinatorTest.ktdocs/RELEASING.md
| CI_KEYSTORE="$RUNNER_TEMP/mora-ci-validation.jks" | ||
| CI_PASSWORD="$(openssl rand -hex 24)" | ||
| CI_ALIAS="mora-ci-validation" | ||
| RELEASE_APK="app/build/outputs/apk/release/app-release.apk" | ||
| RELEASE_MAPPING="app/build/outputs/mapping/release/mapping.prt" | ||
| APKSIGNER="$ANDROID_HOME/build-tools/36.0.0/apksigner" |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
Android Gradle Plugin R8 release mapping output file mapping.txt build/outputs/mapping/release
💡 Result:
When you enable code shrinking and obfuscation (minifyEnabled true) in your Android Gradle project, the R8 compiler automatically generates a mapping file that maps the obfuscated class, method, and field names back to their original names [1][2]. The mapping file is consistently output to a variant-specific directory within your module's build folder [1][3]. For a standard release build, the location is: app/build/outputs/mapping/release/mapping.txt [1][4][2] If you are using build flavors or custom build types, the path adjusts to include those names, following this pattern: [module-name]/build/outputs/mapping/[flavor-name]/[build-type]/mapping.txt [1][4] Key details regarding this file: - Generation: The file is only produced if code shrinking (R8) is enabled for the specific build variant [1]. - Purpose: This file is essential for deobfuscating stack traces from crash reports [2][5]. It should be uploaded to the Google Play Console or third-party crash reporting services (such as Sentry, New Relic, or Shake) whenever you publish a release [6][2][5][7][8]. - Consistency: While many developers still refer to it as a ProGuard mapping file, it is generated by R8, which has replaced ProGuard as the default shrinker in the Android Gradle Plugin [1][2]. - Configuration: You generally do not need to manually specify the output path using -printmapping in your ProGuard rules, as the Android Gradle Plugin handles the output automatically [3]. If you use -printmapping, note that it may be overwritten if multiple variants share the same output path [3].
Citations:
- 1: https://codemia.io/knowledge-hub/path/where_does_android_studio_save_the_proguard_mapping_file
- 2: https://medium.com/@jcjiron4/proguard-is-dead-long-live-r8-understanding-obfuscation-in-android-e4edd8b8feed
- 3: https://r8-docs.preemptive.com/
- 4: https://stackoverflow.com/questions/25724645/where-does-android-studio-save-the-proguard-mapping-file
- 5: https://docs.shakebugs.com/docs/android/crash-reports/deobfuscation/
- 6: https://docs.tracewayapp.com/symbolicator/android
- 7: https://support.newrelic.com/s/hubtopic/aAX8W0000008Ze8WAE/relic-solution-why-is-my-crash-obfuscated-in-new-relic-dashboard-android
- 8: https://docs.sentry.io/platforms/android/configuration/gradle/
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Workflow references:"
rg -n "RELEASE_MAPPING|mapping\.prt|mapping\.txt|Release mapping|mapping/release" .github/workflows android build.gradle *build.gradle* 2>/dev/null || true
echo
echo "Workflow snippets:"
for f in .github/workflows/android.yml .github/workflows/release.yml; do
if [ -f "$f" ]; then
echo "--- $f"
nl -ba "$f" | sed -n '45,70p'
echo "--- $f"
nl -ba "$f" | sed -n '208,232p'
fi
doneRepository: bjcdeshu/mora-markdown
Length of output: 555
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Workflow snippet android.yml:"
awk 'NR>=45 && NR<=100 {printf "%6d\t%s\n", NR, $0}' .github/workflows/android.yml
echo
echo "Workflow snippet release.yml:"
awk 'NR>=212 && NR<=228 {printf "%6d\t%s\n", NR, $0}' .github/workflows/release.yml
echo
echo "Project build/config references:"
rg -n "minifyEnabled|enableR8|proguard|android{[[:space:]]*$|namespace|buildTypes|release" build.gradle settings.gradle app/build.gradle app/src -g '*.gradle' -g 'settings.gradle' -g '*.java' -g '*.kt' 2>/dev/null || trueRepository: bjcdeshu/mora-markdown
Length of output: 3239
Use AGP’s actual R8 mapping filename.
AGP’s release variant mapping artifact is app/build/outputs/mapping/release/mapping.txt; the workflow checks for and uploads mapping.prt, so CI will fail the mapping assertion and the signed release step will miss the intended artifact.
Proposed fix
- RELEASE_MAPPING="app/build/outputs/mapping/release/mapping.prt"
+ RELEASE_MAPPING="app/build/outputs/mapping/release/mapping.txt"- path: app/build/outputs/mapping/release/mapping.prt
+ path: app/build/outputs/mapping/release/mapping.txt📝 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.
| CI_KEYSTORE="$RUNNER_TEMP/mora-ci-validation.jks" | |
| CI_PASSWORD="$(openssl rand -hex 24)" | |
| CI_ALIAS="mora-ci-validation" | |
| RELEASE_APK="app/build/outputs/apk/release/app-release.apk" | |
| RELEASE_MAPPING="app/build/outputs/mapping/release/mapping.prt" | |
| APKSIGNER="$ANDROID_HOME/build-tools/36.0.0/apksigner" | |
| CI_KEYSTORE="$RUNNER_TEMP/mora-ci-validation.jks" | |
| CI_PASSWORD="$(openssl rand -hex 24)" | |
| CI_ALIAS="mora-ci-validation" | |
| RELEASE_APK="app/build/outputs/apk/release/app-release.apk" | |
| RELEASE_MAPPING="app/build/outputs/mapping/release/mapping.txt" | |
| APKSIGNER="$ANDROID_HOME/build-tools/36.0.0/apksigner" |
📍 Affects 2 files
.github/workflows/android.yml#L55-L60(this comment).github/workflows/release.yml#L218-L224
🤖 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 @.github/workflows/android.yml around lines 55 - 60, Update the
RELEASE_MAPPING artifact path in .github/workflows/android.yml (lines 55-60) and
.github/workflows/release.yml (lines 218-224) to use AGP’s actual release
mapping filename, mapping.txt, instead of mapping.prt; keep all checks and
upload steps referencing the corrected RELEASE_MAPPING value.
| if (failure != null) { | ||
| uiState = stateAfterSaveFailure(uiState) | ||
| onResult(DocumentSaveResult.Failed(failure)) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Recompute dirty state after a failed write.
If the user edits back to persistedContent while a save is active, updateContent keeps isDirty true because isSaving is true. A write failure only clears isSaving, leaving the document falsely marked unsaved.
Proposed fix
-internal fun stateAfterSaveFailure(state: DocumentUiState): DocumentUiState =
- state.copy(isSaving = false)
+internal fun stateAfterSaveFailure(
+ state: DocumentUiState,
+ persistedContent: String,
+): DocumentUiState = state.copy(
+ isSaving = false,
+ isDirty = state.hasDocument && (
+ state.uri == null || state.content != persistedContent
+ ),
+)🤖 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 `@app/src/main/java/de/unbow/mora/model/MarkdownViewModel.kt` around lines 383
- 385, Update the failure branch in the save flow around stateAfterSaveFailure
and DocumentSaveResult.Failed to recompute isDirty after isSaving is cleared,
comparing the current content with persistedContent so edits reverted during the
active save are marked clean.
|
Review follow-up:
The full local gate now passes with 64 JVM tests, and the updated head is waiting for a fresh Android CI run. |
Summary
v0.3.2/versionCode 6Scope and non-goals
App Settings remains a scrollable Material bottom sheet. Predictive Back transforms the already laid-out sheet; an off-screen
Englishrow is not forced to reflow into view. Cancellation and completion are verified, so this platform behavior is intentionally accepted without gesture-driven auto-scroll or a custom sheet.This PR does not add a vault, backlinks, graph views, accounts, cloud sync, plugins, syntax highlighting, math, or Mermaid. It does not change signing material, stable tag rules, v0.3.0's blocked Draft, or the public v0.3.1 Release.
Local validation
./gradlew testDebugUnitTest lintDebug assembleDebug: passedgit diff --checkpassedA local protected Release build was not substituted for CI: this Windows host's TLS path could not fetch one Release-only Compose mapping dependency. The PR job now performs the authoritative minified Release lint/build with a disposable non-release key; the tag workflow remains the only v0.3.2 Draft candidate path.
API 36 emulator smoke
On a dedicated Android 16/API 36 gestural-navigation AVD:
MainActivityThe required physical-device check is deliberately after the tag workflow: the exact signed Draft APK must pass one current-phone smoke and the public v0.3.1 → v0.3.2 in-place upgrade before publication.
APK size
The Debug size above is not compared with the signed Release because the build types are not equivalent.
Screenshots
Release plan
mainCI, including the ephemeral minified-Release validation.v0.3.2on the exact finalmaincommit.Summary by CodeRabbit