Skip to content

fix: improve offline editor save safety - #80

Merged
TheZupZup merged 4 commits into
mainfrom
fix/offline-persistence-mobile-ux
Oct 7, 2026
Merged

TheZupZup merged 4 commits into
mainfrom
fix/offline-persistence-mobile-ux

Conversation

@TheZupZup

Copy link
Copy Markdown
Owner

Summary

This PR makes a focused stability pass on NexaNote before more UI work:

  • Flushes pending typed text before switching between Text and Draw mode, so text typed in the debounce window is not skipped when the canvas opens.
  • Saves page text regardless of whether the Draw canvas is currently visible, preventing Text → Draw mode switches from dropping recent text edits.
  • Adds a lightweight Note.copyWith helper for the API DTO so the editor can update its local title snapshot after a successful save.
  • Aligns metadata/com.nexanote.app.yml with the current app/pubspec.yaml version 1.1.4+9.
  • Updates the Android release workflow comment so it matches the current Prepare Release → release PR → tag-from-main flow.

Manual test checklist

  • Install app / open existing NexaNote build.
  • Use offline mode.
  • Create a note.
  • Type text, then immediately tap Draw before autosave delay finishes.
  • Draw strokes.
  • Leave/reopen the note.
  • Confirm typed text still exists.
  • Confirm drawing still exists.
  • Confirm Settings → About still shows the installed version.
  • Confirm Obtainium/GitHub release flow still uses NexaNote-Android.apk.

Notes

This is intentionally not a giant redesign PR. The next PR should do deeper Settings UI polish and canvas gesture work, especially two-finger pan/zoom vs drawing behavior.

@TheZupZup
TheZupZup marked this pull request as ready for review October 7, 2026 07:00
@TheZupZup
TheZupZup merged commit 757466a into main Oct 7, 2026
2 checks passed
@TheZupZup
TheZupZup deleted the fix/offline-persistence-mobile-ux branch October 7, 2026 07:01
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-07T07:07:52.166015Z bf94f1c Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bf94f1cef8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

try {
if (_titleCtrl.text != _note.title) {
await state.updateNoteTitle(_note.id, _titleCtrl.text);
_note = _note.copyWith(title: _titleCtrl.text);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Snapshot the submitted title before awaiting

If the user changes the title again while updateNoteTitle is in flight, this reads the newer controller value even though the request contained the older value. _note.title then incorrectly records the newer title as saved, so subsequent body saves skip updating it and reopening the note restores the stale server title. Capture the submitted title before the await and use that same snapshot for both the request and local note update.

Useful? React with 👍 / 👎.

}

Future<void> _switchToInk() async {
await _flushTextBeforeModeSwitch();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid awaiting an unbounded save before switching modes

With a configured backend and pending edits, tapping Draw cannot update _showInk until the full title/text persistence chain completes. ApiClient.savePageText uses a raw http.put without a timeout, so a slow or unresponsive self-hosted server can leave the editor stuck in Text mode for an OS-level network timeout. Persist a captured snapshot without gating the visual mode change, or apply a bounded timeout.

Useful? React with 👍 / 👎.

_note = _note.copyWith(title: _titleCtrl.text);
}
if (!_showInk) await state.savePageText(_note.id, 1, _contentCtrl.text);
await state.savePageText(_note.id, 1, _contentCtrl.text);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Save the body only when it is dirty

While the note is in Draw mode, a title edit sets _hasChanges and this unconditional call PUTs the body snapshot loaded when the editor opened. If another client has updated that page in the meantime, the title-only save overwrites the newer server body because the text endpoint directly replaces typed_content; the previous Draw-mode path skipped this write. Track title and body dirtiness separately so pending pre-switch text is flushed without resubmitting unchanged cached content.

Useful? React with 👍 / 👎.

_saveTimer?.cancel();
if (!_hasChanges) return;
if (mounted) setState(() => _isSaving = true);
final ok = await _persist();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Serialize the mode-switch save with the active autosave

If the two-second autosave has already started, the user can type a newer body and tap Draw before that request finishes; _hasChanges is still true, so this starts a second _persist concurrently after canceling only the newer timer. The old and new bodies are then PUT independently, and if the older request completes last it overwrites the newer text while both completion paths clear the dirty flag. Queue the latest revision behind an active save rather than launching parallel persistence.

Useful? React with 👍 / 👎.

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.

1 participant