Skip to content

ADFA-3133 Bug fix for over-eager auto-save during onPause event#1151

Merged
hal-eisen-adfa merged 3 commits intostagefrom
ADFA-3133-Editor-writes-files-without-user-assent
Apr 8, 2026
Merged

ADFA-3133 Bug fix for over-eager auto-save during onPause event#1151
hal-eisen-adfa merged 3 commits intostagefrom
ADFA-3133-Editor-writes-files-without-user-assent

Conversation

@hal-eisen-adfa
Copy link
Copy Markdown
Collaborator

Remove implicit save on pause — Delete or gate saveAllAsync in onPause; do not write project files there. Re-evaluate writeOpenedFilesCache / PREF_KEY_OPEN_FILES_CACHE: session restore should not reopen or replace editors in a way that drops undo; prefer remembering paths + selection only when cold-starting, not clobbering live buffers on every onStart after pause.

Resume must not clobber editors — Rework or remove automatic “disk is newer → setText” for open dirty workflows; if external change detection remains, use an explicit conflict dialog, never silent replace while the user may have unsaved work.

Fix updateTabs() — For each non-plugin tab position, use getFileIndexForTabPosition then getEditorAtIndex(fileIndex) for isModified and naming.

Configuration / font scale — EditorActivityKt does not list fontScale; system font changes recreate the activity and lose undo. Prefer adding fontScale (and applying font from preferences in onConfigurationChanged / CodeEditorView) so editors are not torn down, or document that full parity requires a larger state-save story.

Audit — Search for other saveAll, writeTo, or sync triggers tied to lifecycle that are not explicit save or prompted flows.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 4, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a7710d6e-5648-4649-8033-2e0ae0e456d8

📥 Commits

Reviewing files that changed from the base of the PR and between 06f1484 and ad4d7fe.

📒 Files selected for processing (2)
  • app/src/main/AndroidManifest.xml
  • app/src/main/java/com/itsaky/androidide/activities/editor/EditorHandlerActivity.kt
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/src/main/AndroidManifest.xml
  • app/src/main/java/com/itsaky/androidide/activities/editor/EditorHandlerActivity.kt

📝 Walkthrough

Release Notes - Bug Fix for Over-Eager Auto-Save During onPause Event

Key Improvements

  • Removed implicit save on pause: deleted the saveAllAsync(notify = false) call from onPause() so editor buffers are not written to disk automatically; only open-tab state and preference caches are persisted during pause.
  • Hardened external-change detection: checkForExternalFileChanges() now requires the editor buffer to be clean before performing an automatic reload and uses a non-null editor reference when applying content, reducing silent overwrites.
  • Cold-start session restore optimization: onStart() skips replaying the persisted open-files cache when there are already opened editors in memory; the cache is cleared and not reapplied in that case to avoid disrupting live buffers.
  • Prevent resume clobbering: adjusted logic to avoid replacing unsaved buffers during resume flows (automatic "disk is newer → setText" now gated by clean-buffer checks).
  • Corrected plugin-aware tab indexing: updateTabs() now maps tab positions to file indices via getFileIndexForTabPosition() and reads modification state via getEditorAtIndex(...), fixing misaligned names/isModified reporting when plugin tabs exist.
  • Reapply display prefs without full recreation: added CodeEditorView.reapplyEditorDisplayPreferences() and updated onConfigurationChanged() usage so display preferences (font, ligatures, word wrap, etc.) can be reapplied to CodeEditorView children without forcing a full activity teardown.
  • Manifest configChanges extended for fontScale: EditorActivityKt in AndroidManifest gained fontScale in its android:configChanges set so system font scale changes can be handled without immediate activity recreation.

Modified Files

  • app/src/main/AndroidManifest.xml — EditorActivityKt configChanges updated (+fontScale)
  • app/src/main/java/com/itsaky/androidide/activities/editor/EditorHandlerActivity.kt — lifecycle, external-change, and restore logic changes
  • app/src/main/java/com/itsaky/androidide/ui/CodeEditorView.kt — added reapplyEditorDisplayPreferences()
  • app/src/main/java/com/itsaky/androidide/fragments/RecyclerViewFragment.kt — switched to manual FragmentRecyclerviewManualBinding

Risks & Best-Practice Notes

  • Manifest vs. commit-message mismatch: commit text referenced uiMode/density handling, but the manifest change only adds fontScale. Verify whether uiMode/density changes are intentionally omitted or need to be addressed to fully prevent activity recreation in all targeted cases.
  • External-change conflict UX: current gating avoids silent replacement for dirty buffers by only reloading when buffers are clean, but PR objectives requested an explicit conflict dialog for detected external changes. This PR does not add a user-facing conflict prompt — consider implementing a confirmation dialog for external modifications to guarantee no silent data loss.
  • Manual ViewBinding maintenance: replacing generated binding with FragmentRecyclerviewManualBinding increases maintenance burden—layout changes require manual updates to the binding class; document and add tests to reduce drift.
  • Silent cache clearing: skipping and clearing the open-files cache when editors exist happens without user-visible notification; this could surprise users expecting session restoration.
  • Audit incomplete: PR objectives call for an audit for other lifecycle-triggered saves (saveAll, writeTo, sync). This change targets onPause()/related handlers but does not include a project-wide scan — recommend a follow-up code audit to locate other implicit-save paths.

Walkthrough

Editor configuration handling adjusted to avoid activity recreation for font scale changes; editor lifecycle/save logic and external-change reloads tightened; editor display preferences are reapplied after configuration changes; RecyclerView fragment binding replaced with a manual ViewBinding and tab indexing fixes were applied.

Changes

Cohort / File(s) Summary
Manifest / Editor display hook
app/src/main/AndroidManifest.xml, app/src/main/java/com/itsaky/androidide/ui/CodeEditorView.kt
Added fontScale to android:configChanges for the editor activity. Added reapplyEditorDisplayPreferences() to CodeEditorView to reapply display-related preferences safely after config changes.
Editor lifecycle & tab/file handling
app/src/main/java/com/itsaky/androidide/activities/editor/EditorHandlerActivity.kt
Removed automatic async save call in onPause() (persisting only open-tab state), tightened external-file-reload gating to require clean editor buffer and non-null editor instance, skipped restoring cached open-files when editor already has open tabs on start, reapplied display prefs on configuration change, and corrected tab→file indexing when plugin tabs exist.
RecyclerView binding change
app/src/main/java/com/itsaky/androidide/fragments/RecyclerViewFragment.kt, app/src/main/java/com/itsaky/androidide/fragments/FragmentRecyclerviewManualBinding.kt
Replaced generated FragmentRecyclerviewBinding usage with a new manual FragmentRecyclerviewManualBinding exposing the RecyclerView as list/root and updated fragment code to use binding.list instead of binding.root.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • PR #993: Also modifies EditorActivity's android:configChanges (fontScale change is closely related).
  • PR #1057: Alters editor startup/opened-files cache logic and restoration behavior.
  • PR #735: Changes EditorHandlerActivity save/onPause behavior (related to removal of saveAllAsync).

Suggested reviewers

  • itsaky-adfa
  • jatezzz
  • dara-abijo-adfa

Poem

🐰 Hopping through prefs with a careful paw,

font size kept steady without a restart flaw.
Tabs settle down, files mind their place,
RecyclerView hums, binding finds its grace.
I nibble a carrot and applaud the new pace. 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 31.25% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title directly addresses the main issue: removing auto-save during onPause. It matches the primary change in EditorHandlerActivity.kt where saveAllAsync is removed from onPause.
Description check ✅ Passed The description comprehensively covers all changes: saveAllAsync removal from onPause, session restore behavior, external file change handling, updateTabs() fix, and fontScale configuration.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ADFA-3133-Editor-writes-files-without-user-assent

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/com/itsaky/androidide/activities/editor/EditorHandlerActivity.kt (1)

235-248: ⚠️ Potential issue | 🟠 Major

Snapshot mtimes before leaving onPause().

Because this snapshot is launched asynchronously, a write that happens just after onPause() can win the race and become the stored baseline. When that happens, checkForExternalFileChanges() sees no delta on resume and misses the external change entirely.

Suggested fix
 override fun onPause() {
   super.onPause()
-  // Record timestamps for all currently open files before saving the cache
-  val openFiles = editorViewModel.getOpenedFiles()
-  lifecycleScope.launch(Dispatchers.IO) {
-    openFiles.forEach { file ->
-      // Note: Using the file's absolutePath as the key
-      fileTimestamps[file.absolutePath] = file.lastModified()
-    }
-  }
+  editorViewModel.getOpenedFiles().forEach { file ->
+    fileTimestamps[file.absolutePath] = file.lastModified()
+  }
   ActionContextProvider.clearActivity()
   if (!isOpenedFilesSaved.get()) {
     saveOpenedFiles()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/src/main/java/com/itsaky/androidide/activities/editor/EditorHandlerActivity.kt`
around lines 235 - 248, The current onPause() launches an async coroutine to
record fileTimestamps which allows a concurrent write to race and become the
baseline; instead capture a synchronous snapshot of opened file paths and their
lastModified() values on the caller thread before launching any background work,
then hand that immutable snapshot into lifecycleScope.launch(Dispatchers.IO) to
persist it; update the onPause() flow that calls
editorViewModel.getOpenedFiles(), writes into fileTimestamps, and uses
lifecycleScope.launch(Dispatchers.IO) so the snapshot is computed synchronously
but the disk/storage persistence remains asynchronous.
🧹 Nitpick comments (1)
app/src/main/java/com/itsaky/androidide/fragments/RecyclerViewFragment.kt (1)

168-175: attachToParent parameter is ignored.

The inflate() method accepts attachToParent but always passes false to inflater.inflate(). While current callers in FragmentWithBinding always pass false, this deviates from standard ViewBinding.inflate() behavior and could be misleading.

Consider either honoring the parameter (with appropriate handling for the true case where inflate() returns the parent) or adding a comment explaining why it's intentionally ignored.

💡 Option: Add clarifying comment
 		fun inflate(
 			inflater: LayoutInflater,
 			parent: ViewGroup?,
 			attachToParent: Boolean,
 		): FragmentRecyclerviewManualBinding {
+			// Note: attachToParent is intentionally ignored; FragmentWithBinding always passes false.
+			// Honoring true would require special handling since inflate() returns parent, not the inflated view.
 			val root = inflater.inflate(R.layout.fragment_recyclerview, parent, false) as RecyclerView
 			return FragmentRecyclerviewManualBinding(root)
 		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/com/itsaky/androidide/fragments/RecyclerViewFragment.kt`
around lines 168 - 175, The inflate() function in
FragmentRecyclerviewManualBinding currently ignores the attachToParent parameter
and always calls inflater.inflate(..., false); update inflate(inflater:
LayoutInflater, parent: ViewGroup?, attachToParent: Boolean) to either honor
attachToParent (i.e., pass attachToParent into inflater.inflate and handle the
returned View when attachToParent is true) or add a clear comment above
FragmentRecyclerviewManualBinding.inflate explaining why attachToParent is
intentionally ignored; locate the inflater.inflate(...) call in the inflate
method and either replace the hardcoded false with the attachToParent parameter
and adjust return handling for the parent-attached case, or add the explanatory
comment referencing FragmentRecyclerviewManualBinding.inflate to justify the
current behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/src/main/AndroidManifest.xml`:
- Around line 95-97: The manifest opts out of uiMode and density configuration
changes for the activity declared as
android:name=".activities.editor.EditorActivityKt", but onConfigurationChanged()
only calls configureEditorIfNeeded()/reapplyEditorDisplayPreferences() and does
not rebind theme/density-dependent state (e.g.,
SchemeAndroidIDE.newInstance(context), CodeEditorView initialization or
SizeUtils.dp2px-based drawables), so remove uiMode and density from the
android:configChanges attribute (leave fontScale so system font-scale handling
remains) to force activity recreation until full UI rebinding is implemented.

In
`@app/src/main/java/com/itsaky/androidide/activities/editor/EditorHandlerActivity.kt`:
- Around line 277-302: The reload is incorrectly gated by
IDEEditor.canUndo()/canRedo() so clean buffers that still have undo history
(after markAsSaved()/markUnmodified()) are skipped; in
checkForExternalFileChanges() remove or replace the early return that checks
ideEditor.canUndo() || ideEditor.canRedo() so that when editorView.isModified is
false the file is reloaded (call ideEditor.setText(newContent) and
editorView.markAsSaved()); if you must preserve undo history instead of
discarding it, replace that early-return with an explicit user conflict
prompt/confirmation flow (show dialog) rather than silently ignoring the
external change—refer to checkForExternalFileChanges(),
CodeEditorView.markAsSaved(), and IDEEditor.canUndo()/canRedo()/markUnmodified()
when making the change.

---

Outside diff comments:
In
`@app/src/main/java/com/itsaky/androidide/activities/editor/EditorHandlerActivity.kt`:
- Around line 235-248: The current onPause() launches an async coroutine to
record fileTimestamps which allows a concurrent write to race and become the
baseline; instead capture a synchronous snapshot of opened file paths and their
lastModified() values on the caller thread before launching any background work,
then hand that immutable snapshot into lifecycleScope.launch(Dispatchers.IO) to
persist it; update the onPause() flow that calls
editorViewModel.getOpenedFiles(), writes into fileTimestamps, and uses
lifecycleScope.launch(Dispatchers.IO) so the snapshot is computed synchronously
but the disk/storage persistence remains asynchronous.

---

Nitpick comments:
In `@app/src/main/java/com/itsaky/androidide/fragments/RecyclerViewFragment.kt`:
- Around line 168-175: The inflate() function in
FragmentRecyclerviewManualBinding currently ignores the attachToParent parameter
and always calls inflater.inflate(..., false); update inflate(inflater:
LayoutInflater, parent: ViewGroup?, attachToParent: Boolean) to either honor
attachToParent (i.e., pass attachToParent into inflater.inflate and handle the
returned View when attachToParent is true) or add a clear comment above
FragmentRecyclerviewManualBinding.inflate explaining why attachToParent is
intentionally ignored; locate the inflater.inflate(...) call in the inflate
method and either replace the hardcoded false with the attachToParent parameter
and adjust return handling for the parent-attached case, or add the explanatory
comment referencing FragmentRecyclerviewManualBinding.inflate to justify the
current behavior.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7a62c8af-bb8d-4ba8-84d9-8ba366b6abb2

📥 Commits

Reviewing files that changed from the base of the PR and between c85bf67 and 06f1484.

📒 Files selected for processing (4)
  • app/src/main/AndroidManifest.xml
  • app/src/main/java/com/itsaky/androidide/activities/editor/EditorHandlerActivity.kt
  • app/src/main/java/com/itsaky/androidide/fragments/RecyclerViewFragment.kt
  • app/src/main/java/com/itsaky/androidide/ui/CodeEditorView.kt

Comment thread app/src/main/AndroidManifest.xml
…Mode/density unless resources are fully rebound
@hal-eisen-adfa hal-eisen-adfa requested a review from a team April 6, 2026 23:11
@hal-eisen-adfa hal-eisen-adfa merged commit 82c6e60 into stage Apr 8, 2026
2 checks passed
@hal-eisen-adfa hal-eisen-adfa deleted the ADFA-3133-Editor-writes-files-without-user-assent branch April 8, 2026 06:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants