Skip to content

Skip autosave when entry content hasn't actually changed - #164

Merged
patrickunterwegs merged 4 commits into
mainfrom
implement-undo-functionality
Oct 2, 2026
Merged

patrickunterwegs merged 4 commits into
mainfrom
implement-undo-functionality

Conversation

@patrickunterwegs

@patrickunterwegs patrickunterwegs commented Sep 29, 2026 •

Copy link
Copy Markdown
Member

Summary

  • Opening the date/time picker without touching it, or re-tapping an already-selected color/status, re-fired the corresponding edit handler with an unchanged value, which still bumped lastModified and triggered the debounced autosave - pushing a spurious checkpoint onto changeBackstack and making Undo appear active despite nothing having changed.
  • Added IcalEntry.hasSameContentAs() (ignores id/lastModified/syncState/etag/href) and gated the autosave pipeline in DetailsViewModel on it, so a save is skipped whenever the entry's content genuinely hasn't changed - except for undo's own restore, which is tracked via undoRestoreMarker and must still reach the database.
  • TimePickerBottomSheet had an independent bug: its LaunchedEffect unconditionally cleared isDateOnly even on its first, untouched firing, so merely opening the time picker on a date-only entry silently gave it a time. Fixed by skipping that initial firing.

Test plan

  • ./gradlew :shared:allTests :androidJournalsApp:lintDebug :androidNotesApp:lintDebug :androidTasksApp:lintDebug
  • Open an existing entry with a set date/time; open the date picker and dismiss without picking - Undo stays disabled
  • Same for the time picker - Undo stays disabled, and a date-only entry stays date-only
  • Tap the already-selected color / already-active status chip - Undo stays disabled
  • Make a genuine edit, click Undo - reverts and persists (survives navigating away and reopening)

🤖 Generated with Claude Code

https://claude.ai/code/session_01N4dVR3vYtJjSRNLvzXfHrh


Generated by Claude Code

patrickunterwegs and others added 4 commits September 29, 2026 12:35
saveIcalEntry() pushed the just-saved (current) entry onto
changeBackstack, so its last element always duplicated the current
state - the first undo click was a no-op. Worse, onUndo() stamped the
restored entry with a fresh lastModified, which the 500ms-debounced
autosave pipeline treated as a new edit and re-saved ~500ms later,
re-appending it onto changeBackstack and undoing the pop. Undo only
appeared to work if clicked faster than that autosave delay.

onUndo() now targets the checkpoint before the duplicated top of the
stack, and marks its own restore (by the timestamp it just set) so
saveIcalEntry() can recognize and skip recording that specific
follow-up save instead of racing it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N4dVR3vYtJjSRNLvzXfHrh
Every edit handler in DetailsViewModel unconditionally bumped
lastModified whenever it was called, on the assumption it's only
called on a genuine change. Two UI call sites break that assumption:
opening the date or time picker without touching it re-fires
onDateSelected/onTimeUpdated with the picker's seeded (unchanged)
value via LaunchedEffect running on first composition, and re-tapping
an already-selected color/status re-fires those handlers too. Each
still triggered the debounced autosave, which pushed a checkpoint
onto changeBackstack and made Undo appear active despite nothing
having changed.

Rather than guard every handler individually, add one content-equality
check (IcalEntry.hasSameContentAs, ignoring id/lastModified/syncState/
etag/href) at the single choke point all edits already funnel through:
the debounced autosave pipeline. It's skipped only when this save is
undo's own restore (tracked via undoRestoreMarker), since undo
legitimately restores content that already matches a backstack entry
and that save must still reach the database.

TimePickerBottomSheet had a second, independent bug its own
LaunchedEffect unconditionally cleared isDateOnly even on that same
first, untouched firing, so merely opening the time picker on a
date-only entry silently gave it a time. That's a genuine content
change no equality check can catch, so its LaunchedEffect now skips
its first (seed) firing instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N4dVR3vYtJjSRNLvzXfHrh
@patrickunterwegs
patrickunterwegs merged commit d284211 into main Oct 2, 2026
1 check passed
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