Skip to content

Complete offline library sync and fix annotation editing and favorites - #23

Merged
Eoic merged 4 commits into
masterfrom
feature/powersync-library-sync
Sep 5, 2026
Merged

Complete offline library sync and fix annotation editing and favorites#23
Eoic merged 4 commits into
masterfrom
feature/powersync-library-sync

Conversation

@Eoic

@Eoic Eoic commented Sep 5, 2026

Copy link
Copy Markdown
Member

Ticket

No linked ticket.

Summary

Library changes now persist locally and synchronize between devices for books, shelves, topics, notes, annotations, bookmarks, and memberships. Guest data remains local, while account/server switches clear prior library views and invalidate stale editor handles.

  • Use typed repositories and reactive PowerSync snapshots; await local writes before dismissing editors and retain failed saves for retry.
  • Synchronize independently editable book fields, preserve custom metadata/media references, and keep device paths and temporary covers local.
  • Preserve existing book databases and queued uploads during schema expansion, including legacy metadata and explicit nulls.
  • Open the complete prefilled Edit annotation form, persist physical-book bookmarks, and save favorites through the book repository.
  • Remove obsolete planning/spec documents.

Testing

  • Flutter suite: 1,080 passed, 10 skipped; Flutter analysis passed.
  • Analysis and the full suite were rerun after merging current master, using installed Flutter 3.44.8. The target branch's SDK-pinned lockfile entries are preserved.
  • Release web build passed with icon tree shaking.
  • Against the local server/PowerSync stack, run PAPYRUS_LIVE_SYNC=1 flutter test test/powersync/library_live_sync_test.dart --reporter expanded from app/.
  • The live test passed with two independent clients and a separate account: create/edit all domains, restart with queued offline changes, verify field merges/null clearing, and confirm deletion defeats stale edits. It also verifies favorites and physical-book bookmarks. Allow over a minute for configured upload-rate-limit retries.

UI evidence

Widget tests cover the annotation menu opening the full prefilled editor and saving every editable field, plus delayed/failed bookmark saves. The requester confirmed the fixes work during manual testing.

Compatibility and migration

Depends on PapyrusReader/server#5. Deploy the server migrations/upload handlers, refresh publication/grants, and activate the expanded sync configuration before releasing this client. No reset, key regeneration, or automatic guest import is required. sqlite_async is now declared directly at its existing locked version.

Notes

Reading sessions/goals, standalone series management, guest import, conflict-resolution UI, and expanded file-transfer behavior remain outside this change. Existing series descriptors on books synchronize. Latest master platform/build fixes are included.

Workspace integration: PapyrusReader/papyrus#1.

Checklist

  • The branch, commits, and pull request title follow the repository conventions.
  • The change is complete and limited to the requested scope.
  • Relevant tests have been added or updated.
  • Formatting, analysis, and tests pass.
  • Documentation, UI evidence, and migration notes are included where relevant.
  • No credentials, personal data, copyrighted fixtures, or unrelated generated files are included.
  • The branch is up to date with its target branch.

Copilot AI lite review requested due to automatic review settings September 5, 2026 23:03
@Eoic Eoic self-assigned this Sep 5, 2026
@Eoic
Eoic merged commit 05f8967 into master Sep 5, 2026
2 checks passed

Copilot AI 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.

🔵 Needs a closer look

It expands the sync schema and persistence flow across many entities/UI paths, and the remaining issues (snapshot trigger query cost and favorite-toggle consistency) warrant careful human review before merging.

Pull request overview

This PR advances Papyrus’s local-first “offline library” by expanding the synced PowerSync schema beyond books (shelves/topics/notes/annotations/bookmarks/memberships), adding typed repositories + snapshot watching, and updating editors/UI actions to await persistence (or remain open on failure) so users don’t lose changes while offline or during profile switches.

Changes:

  • Expand PowerSync schema + mapping to support multi-entity library sync, legacy metadata promotion, and explicit-null handling.
  • Introduce persistent editor/save behavior across sheets/dialogs (await writes, keep UI open on failure, retry-friendly).
  • Fix/adjust UX flows for annotation editing, physical-book bookmark persistence, and favorites persistence.
File summaries
File Description
docs/superpowers/specs/2026-08-23-book-storage-status-design.md Remove obsolete spec document.
docs/superpowers/specs/2026-08-23-book-import-sheet-refactor-design.md Remove obsolete spec document.
docs/superpowers/specs/2026-08-23-book-import-drop-zone-design.md Remove obsolete spec document.
docs/superpowers/specs/2026-08-01-shelves-page-controls-design.md Remove obsolete spec document.
docs/superpowers/specs/2026-08-01-shelf-page-heading-design.md Remove obsolete spec document.
docs/superpowers/specs/2026-08-01-reusable-shelf-books-page-design.md Remove obsolete spec document.
docs/superpowers/specs/2026-08-01-book-import-workflow-rework-design.md Remove obsolete spec document.
docs/superpowers/specs/2026-08-01-advanced-filter-sheet-sizing-design.md Remove obsolete spec document.
docs/superpowers/specs/2026-07-30-advanced-library-filters-visual-redesign-design.md Remove obsolete spec document.
docs/superpowers/specs/2026-07-27-reader-integration-design.md Remove obsolete spec document.
docs/superpowers/specs/2026-07-24-acquisition-bottom-sheets-design.md Remove obsolete spec document.
docs/superpowers/specs/2026-07-14-import-add-loading-state-design.md Remove obsolete spec document.
docs/superpowers/specs/2026-07-14-import-action-button-shapes-design.md Remove obsolete spec document.
docs/superpowers/specs/2026-07-14-add-book-backdrop-continuity-design.md Remove obsolete spec document.
docs/superpowers/specs/2026-07-13-book-edit-responsive-pane-design.md Remove obsolete spec document.
docs/superpowers/specs/2026-07-13-add-book-bottom-sheets-design.md Remove obsolete spec document.
docs/superpowers/specs/2026-07-11-cover-image-cache-design.md Remove obsolete spec document.
docs/superpowers/plans/2026-08-23-book-import-drop-zone.md Remove obsolete plan document.
docs/superpowers/plans/2026-08-01-shelf-page-heading.md Remove obsolete plan document.
docs/superpowers/plans/2026-08-01-advanced-filter-sheet-sizing.md Remove obsolete plan document.
docs/superpowers/plans/2026-07-27-reader-integration.md Remove obsolete plan document.
docs/superpowers/plans/2026-07-14-import-add-loading-state.md Remove obsolete plan document.
docs/superpowers/plans/2026-07-14-import-action-button-shapes.md Remove obsolete plan document.
docs/superpowers/plans/2026-07-14-add-book-backdrop-continuity.md Remove obsolete plan document.
docs/superpowers/plans/2026-07-13-book-edit-responsive-pane.md Remove obsolete plan document.
docs/superpowers/plans/2026-07-13-add-book-bottom-sheets.md Remove obsolete plan document.
docs/superpowers/plans/2026-07-12-book-edit-layout-alignment.md Remove obsolete plan document.
app/test/widgets/persistent_editor_test.dart Add widget coverage ensuring editors await persistence and remain open on failure.
app/test/widgets/book_details/bookmark_dialog_test.dart Add widget coverage for bookmark create/edit/delete + persistence waiting.
app/test/providers/library_favorite_persistence_test.dart Add provider+PowerSync tests for favorite persistence, scope, and remote changes.
app/test/providers/bookmark_persistence_test.dart Add provider tests for bookmark merges + stale-handle/profile-switch isolation.
app/test/providers/book_edit_persistence_test.dart Add provider tests for stale editor save behavior and scope isolation.
app/test/providers/book_details_provider_test.dart Update tests to await async CRUD and ensure remote note-only changes notify.
app/test/powersync/powersync_book_mapper_test.dart Extend mapper tests for promoted fields, null semantics, cover URL filtering.
app/test/powersync/papyrus_schema_mode_test.dart Update schema tests for multi-table library schemas (guest localOnly, account synced).
app/test/powersync/bookmark_persistence_test.dart Add PowerSync persistence/merge/isolation tests for bookmarks.
app/test/pages/annotation_edit_test.dart Add widget test ensuring full annotation editor opens prefilled and saves all fields.
app/test/models/shelf_icon_test.dart Add tests for shelf icon descriptor round-tripping + unsupported icon identity preservation.
app/test/models/library_nullable_fields_test.dart Add tests for explicit clearing of nullable fields across models.
app/pubspec.yaml Make sqlite_async a direct dependency.
app/pubspec.lock Record sqlite_async as direct main dependency.
app/lib/widgets/topics/topic_detail_sheet.dart Make topic edit/delete await repository writes and show error feedback on delete failure.
app/lib/widgets/topics/manage_topics_sheet.dart Await saves, use persistent-save behavior, and UUID ids for new topics.
app/lib/widgets/topics/add_topic_sheet.dart Make onSave async-capable and keep sheet open until persistence succeeds.
app/lib/widgets/shelves/move_to_shelf_sheet.dart Await saves, use persistent-save behavior, and UUID ids for new shelves.
app/lib/widgets/shelves/add_shelf_sheet.dart Make onSave async-capable and keep sheet open until persistence succeeds.
app/lib/widgets/shared/persistent_save.dart Introduce shared “persist before dismiss” mixin with generic failure snackbar.
app/lib/widgets/library/book_grid.dart Route favorite toggles through shared favorite persistence helper.
app/lib/widgets/bookmarks/bookmark_action_sheet.dart Make bookmark note/color/delete await persistence; keep UI open on failures.
app/lib/widgets/book/book_details.dart Persist memberships via centralized updateBookMemberships rather than manual add/remove loops.
app/lib/widgets/book_details/note_dialog.dart Make note save await persistence and use UUID ids; disable actions while saving.
app/lib/widgets/book_details/bookmark_dialog.dart Make bookmark save await persistence and use UUID ids; disable actions while saving.
app/lib/widgets/book_details/annotation_dialog.dart Make annotation save await persistence and use UUID ids; preserve immutable location fields when editing.
app/lib/widgets/book_details/annotation_action_sheet.dart Rename “Edit note” to “Edit annotation” and adjust action enum.
app/lib/widgets/annotations/annotation_action_sheet.dart Make annotation note editing await persistence and keep sheet open on failure.
app/lib/utils/bulk_book_actions.dart Persist bulk shelf/topic membership changes via repository writer; make bulk favorite toggle awaited + error surfaced.
app/lib/utils/book_actions.dart Add toggleBookFavorite helper with awaited persistence + error snackbar; use it from context menu.
app/lib/providers/shelves_provider.dart Switch shelf CRUD + reorder to async persistence with repository support; use UUIDs.
app/lib/providers/notes_provider.dart Make note CRUD async and repository-aware; pin toggles now await persistence.
app/lib/providers/library_provider.dart Make favorites persistence async; optionally source favorite state directly from DataStore.
app/lib/providers/bookmarks_provider.dart Make bookmark CRUD async + repository-aware with “previous” support.
app/lib/providers/book_edit_provider.dart Save through updateBookAndWait with captured repository handle to avoid stale writes.
app/lib/providers/book_details_provider.dart Make details CRUD async + repository-aware; always notify on DataStore changes for child-record updates.
app/lib/providers/annotations_provider.dart Make annotation CRUD async + repository-aware with explicit-null clearing.
app/lib/powersync/powersync_service.dart Implement LibraryRepository, watch multi-table library snapshots, and clear guest library across all library tables.
app/lib/powersync/powersync_book_mapper.dart Promote additional book fields into synced columns; preserve explicit null/absent fields; strengthen remote cover URL filtering.
app/lib/powersync/papyrus_schema.dart Expand PowerSync schemas to include shelves/tags/notes/annotations/bookmarks/membership tables (+ local migrations table).
app/lib/powersync/papyrus_powersync_connector.dart Encode non-book CRUD payloads via shared library row decoding.
app/lib/powersync/library_row_mapper.dart Add shared row encode/decode + per-entity mappers and library table list.
app/lib/pages/shelves_page.dart Await shelf create/edit/delete persistence and surface delete failures via snackbar.
app/lib/pages/shelf_contents_page.dart Await shelf edit persistence with repository + previous value supplied.
app/lib/pages/notes_page.dart Route note edits through persistence-aware dialog callback; await delete and surface failures.
app/lib/pages/bookmarks_page.dart Route bookmark note/color/delete through persistence-aware callbacks; await deletes.
app/lib/pages/book_details_page.dart Route note/bookmark/annotation CRUD through persistence-aware dialogs; make annotation editing open full editor.
app/lib/pages/annotations_page.dart Route annotation edit through full editor; await deletes and surface failures.
app/lib/models/tag.dart Support explicit clearing of nullable description in copyWith.
app/lib/models/shelf.dart Add ShelfIconDescriptor for stable identity + explicit nullable clears; preserve unsupported icon descriptors.
app/lib/models/note.dart Support explicit clearing of nullable location in copyWith.
app/lib/models/book.dart Support explicit clearing of additional nullable metadata fields in copyWith.
app/lib/models/annotation.dart Support explicit clearing of nullable note in copyWith.
app/lib/main.dart Provide DataStore to LibraryProvider so favorites can persist through the repository.
app/lib/data/repositories/library_repository.dart Introduce typed repository interfaces for non-book entities and membership writes.
Review details
  • Files reviewed: 95/96 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines 282 to +284
_booksSubscription = database
.watch('SELECT * FROM books ORDER BY added_at DESC', triggerOnTables: ['books'])
.listen((rows) {
_booksController.add(rows.map((row) => PowerSyncBookMapper.fromRow(Map<String, Object?>.from(row))).toList());
});
.watch('SELECT count(*) FROM books', triggerOnTables: libraryTableNames)
.asyncMap((_) => library.snapshot())
Comment on lines +76 to +86
Future<void> toggleBookFavorite(BuildContext context, String bookId, bool currentFavorite) async {
try {
await context.read<LibraryProvider>().toggleFavorite(bookId, currentFavorite);
} catch (_) {
if (context.mounted) {
ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text('Could not save favorite. Please try again.')));
}
}
}
@Eoic
Eoic deleted the feature/powersync-library-sync branch September 6, 2026 00:08
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