Skip to content

feat: note export/import, Android backup, Firebase distribution - #78

Draft
enlorik wants to merge 10 commits into
mainfrom
feature/android-tablet-safety
Draft

feat: note export/import, Android backup, Firebase distribution#78
enlorik wants to merge 10 commits into
mainfrom
feature/android-tablet-safety

Conversation

@enlorik

@enlorik enlorik commented Jul 24, 2026

Copy link
Copy Markdown
Owner

Export/import notes via share picker (versioned JSON), Android backup rules for grepink.db, persistent release signing via CI secrets, Firebase distribution workflow. 601 to 637 tests, flutter analyze clean.

…distribution

Adds Settings → Export notes (share as JSON via Android share picker) and
Import notes (merge by ID keeping the newer version, or Replace all with a
double-confirmation). The export format is versioned so we can detect future
schema changes at import time. Malformed files are rejected with a clear error.

Adds Android Auto Backup rules (backup_rules.xml, data_extraction_rules.xml)
so grepink.db survives device transfers; flutter_secure_storage entries are
excluded from backup.

Replaces the debug signingConfig in build.gradle with a persistent keystore
loaded from android/key.properties locally or ANDROID_PREVIEW_* environment
variables in CI. The build fails with a clear message rather than producing an
APK silently signed with the wrong key.

Adds a manually triggered firebase-distribute workflow that runs analyze +
tests, builds a signed release APK (versionCode = run number), uploads to
Firebase App Distribution, and also keeps the APK as a GitHub artifact.

Adds docs/android_signing.md and docs/firebase_setup.md with the exact steps
needed to complete setup (keystore generation, secrets, Firebase project
registration). Includes 36 new tests covering round-trip, malformed-file,
merge, replace, schema stability, and update-over-install scenarios.
@enlorik

enlorik commented Jul 24, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@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: ac9a1e691e

ℹ️ 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".

Comment thread .github/workflows/firebase-distribute.yml
Comment thread lib/services/note_export_service.dart
Decode the keystore to android/app/preview.jks so Gradle's file() resolves
it correctly relative to the :app project directory.

Force embeddingPending=true on every note written during import (both merge
and replace-all paths) so restored notes are re-embedded by the next indexing
pass and appear in semantic search.
@enlorik

enlorik commented Jul 24, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@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: 9016f98f2a

ℹ️ 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".

Comment thread lib/screens/settings_screen.dart Outdated
The previous loop iterated over output.notes (all merged notes) and
wrote every note with clearEmbedding:true, silently destroying embeddings
on notes that were skipped (existing was same age or newer). Rewrite the
merge path to iterate over incoming only and skip notes where the existing
version is already newer or equal, leaving untouched notes intact.
@enlorik

enlorik commented Jul 24, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@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: d15f0eccfd

ℹ️ 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".

Comment thread .github/workflows/firebase-distribute.yml Outdated
Pass FIREBASE_APP_ID and RELEASE_NOTES through environment variables rather
than interpolating GitHub Actions expressions directly into the shell script.
This closes a path where a crafted release-notes value could execute arbitrary
commands inside the upload step.
@enlorik

enlorik commented Jul 24, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@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: f35893c54e

ℹ️ 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".

Comment thread lib/screens/settings_screen.dart Outdated
Comment thread lib/screens/settings_screen.dart Outdated
Replace ref.read(notesProvider).valueOrNull ?? [] with
DatabaseService.instance.getAllNotes() in both _exportNotes and _importNotes.
The provider state is null while loading; falling back to [] would export an
empty file or treat every incoming note as new during a merge.
@enlorik

enlorik commented Jul 24, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@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: 3d48a3a0b7

ℹ️ 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".

Comment thread lib/screens/settings_screen.dart Outdated
Comment thread lib/screens/settings_screen.dart Outdated
Wrap the replace-all clear+inserts in a database transaction via
DatabaseService.replaceAll() so a mid-import storage error cannot leave the
user with no notes and no backup (the old data is kept if the transaction
rolls back).

Add NotesNotifier.reindexPendingNotes() which embeds only notes that have
embeddingPending=true, triggered at the end of both import paths so restored
notes appear in semantic search automatically without needing a manual reindex.
@enlorik

enlorik commented Jul 24, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@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: b388e42232

ℹ️ 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".

Comment thread lib/screens/settings_screen.dart Outdated
Comment thread lib/screens/settings_screen.dart Outdated
…lity

Replace the temp-file-write export path with XFile.fromData so the export
works without dart:io or path_provider. Simplify the import bytes path to
require bytes (FilePicker always provides them when withData:true), removing
the File(path).readAsString() fallback. Settings screen now compiles on web.
@enlorik

enlorik commented Jul 24, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@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: 086788c618

ℹ️ 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".

recommended immediate recovery method.
-->
<full-backup-content>
<include domain="database" path="grepink.db" />

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 Include SQLite WAL files in backups

When Android backups run after recent note writes, this rule backs up only grepink.db; Android's Auto Backup rules state that adding an <include> limits backup to the listed paths, and Android 9+ can keep committed SQLite changes in WAL sidecars by default. Because the matching grepink.db-wal/grepink.db-shm files are not included here or in data_extraction_rules.xml, a cloud or device-transfer restore can come back stale or inconsistent for recently saved notes; include the whole database directory/sidecars or force checkpointing before relying on backup.

Useful? React with 👍 / 👎.

Comment thread android/app/build.gradle
buildTypes {
release {
signingConfig signingConfigs.debug
signingConfig signingConfigs.release

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 Preserve the update path for existing installs

For any tablet that already has an APK built before this commit, release builds were signed with Gradle's debug config, but this switches future releases to the new preview keystore. Android rejects over-installs signed by a different certificate, so those users cannot follow the documented data-preserving update path unless they first export/uninstall/import or the CI secret is seeded with the same prior signing key; call out this one-time migration or keep the signing key compatible for existing testers.

Useful? React with 👍 / 👎.

Comment on lines +93 to +97
firebase appdistribution:distribute \
build/app/outputs/flutter-apk/app-release.apk \
--app "$FIREBASE_APP_ID" \
--release-notes "$RELEASE_NOTES" \
--token "$FIREBASE_TOKEN"

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 Target a tester group during distribution

In the inspected Firebase App Distribution workflow, the upload command only passes the APK, app id, release notes, and token. Firebase's CLI docs describe --testers/--groups as the options used to invite testers or tester groups, so manual runs will create/upload a release but won't assign it to the tester group created in the setup checklist unless someone does that later in the console; add a group/testers secret or workflow input to the command.

Useful? React with 👍 / 👎.

enlorik added 3 commits July 24, 2026 23:21
…tester groups

Include grepink.db-wal and grepink.db-shm in backup_rules.xml and
data_extraction_rules.xml so Android Auto Backup captures SQLite WAL
sidecars alongside the main database file.

Add a 'First build from a previously debug-signed install' section to
docs/android_signing.md describing the one-time export-uninstall-import
migration for existing testers.

Add optional FIREBASE_TESTER_GROUPS secret support to the distribution
workflow so the uploaded release is assigned to a tester group automatically.
Update docs/firebase_setup.md to list the new optional secret.
Enable WAL journal mode via onConfigure so Android Auto Backup always
captures a consistent DB snapshot. Without it, DELETE-mode mid-write
could produce a corrupt restore image despite the WAL sidecar rules.

Add DatabaseService.mergeNotes() wrapping the merge loop in a single
transaction, matching the atomicity guarantee already present in
replaceAll(). Replace the non-atomic insertNote/updateNote loop in
_importNotes() with a single mergeNotes() call.

Fix NoteExportService.merge() to apply copyWith(embeddingPending:true)
on added/updated notes so any caller gets notes that will be queued for
re-embedding rather than silently remaining unembedded.

Consolidate _importNotes() error handling: utf8.decode(), getAllNotes(),
and preview() now share a single try/catch so non-UTF-8 files and DB
errors show a SnackBar instead of silently propagating unhandled.

Move both reindexPendingNotes() calls to before their !mounted guards
so navigating away during a slow import does not leave all imported
notes permanently stuck with embeddingPending=true.
…get safe

Split the pre-dialog try/catch in _importNotes() so that file parsing
errors show "Invalid backup file" and database read errors show "Could
not read notes", rather than both showing the file message.

Move loadNotes() outside the write try/catch so a transient read error
after a successful replaceAll()/mergeNotes() does not incorrectly
report the import as failed when the data was already written.

Wrap the outer body of reindexPendingNotes() (settingsProvider read
and getNotesWithPendingEmbeddings) in a top-level try/catch, matching
the pattern of _triggerEmbedding, so callers can safely fire-and-forget
without risking unhandled Future errors from infrastructure failures.
@enlorik

enlorik commented Jul 25, 2026

Copy link
Copy Markdown
Owner Author

@codex review

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