Skip to content

feat: upgrade import/export system for media support and third-party apps#35

Open
mehradotdev wants to merge 1 commit intomainfrom
feat.update-import-export
Open

feat: upgrade import/export system for media support and third-party apps#35
mehradotdev wants to merge 1 commit intomainfrom
feat.update-import-export

Conversation

@mehradotdev
Copy link
Copy Markdown
Owner

@mehradotdev mehradotdev commented Apr 12, 2026

Closes #30

Summary

  • Transitioned primary backup format from CSV to ZIP archives to safely include media files (images, audio memos) alongside entries.
  • Implemented a new import pipeline supporting data from "Gratitude App".
  • Refactored the Backup & Restore settings UI with dedicated options for Tackbok .zip, Gratitude App .zip, and Presently .csv files.
  • Introduced new interactive modals for import mode selection, real-time progress tracking, and detailed post-import summaries.
  • Added custom SVG icons (GratitudeJournal, Presently) for third-party tools.
  • Updated translations across all supported locales for new UI strings.

Summary by CodeRabbit

  • New Features
    • ZIP-based backup and restore functionality replaces CSV format
    • Import existing journals from Gratitude App
    • Import entries from Presently CSV files
    • Real-time import progress tracking with detailed metrics
    • Import summary showing entries, tags, prompts, photos, and audio files imported

…apps

- Transitioned primary backup format from CSV to ZIP archives to safely include media files (images, audio memos) alongside entries.
- Implemented a new import pipeline supporting data from "Gratitude App".
- Refactored the Backup & Restore settings UI with dedicated options for Tackbok .zip, Gratitude App .zip, and Presently .csv files.
- Introduced new interactive modals for import mode selection, real-time progress tracking, and detailed post-import summaries.
- Added custom SVG icons (GratitudeJournal, Presently) for third-party tools.
- Updated translations across all supported locales for new UI strings.
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 12, 2026

📝 Walkthrough

Walkthrough

Replaces CSV-based backup/import with ZIP-based export and multi-source import system supporting Tackbok ZIP backups, Gratitude App imports, and Presently CSV imports. Adds comprehensive ZIP codec implementation, import progress tracking UI, and profile settings storage. Removes legacy CSV backup module.

Changes

Cohort / File(s) Summary
Icon Components
apps/mobile/src/components/ImportSourceIcons.tsx
Added two SVG logo icon components (GratitudeJournalLogoIcon, PresentlyLogoIcon) for import source display.
ZIP Archive Utilities & Codec
apps/mobile/src/lib/zip/*
Complete ZIP implementation: core archive parse/read/write utilities, DEFLATE codec (compression/decompression), CRC32 checksums, byte encoding (UTF-8/IBM/ASCII), and entry builders. ~2000 LOC supporting ZIP creation and extraction.
Backup Export (ZIP-Based)
apps/mobile/src/lib/backupExport/tackbok.ts, portable.ts, index.ts
New ZIP-based backup export: collects entries/tags/prompts/profile from database, creates portable representations with media counting, builds ZIP archive with manifest/entries/tags/prompts/profile JSON plus media files, saves via timestamped filename.
Backup Import (Multi-Source)
apps/mobile/src/lib/backupImport/types.ts, archiveUtils.ts, progress.ts, portable.ts
Type system (import phases/sources/modes/summary), shared archive utilities (ZIP I/O, asset file handling, path validation), progress reporting mechanism, and portable data adapter for Tackbok/Gratitude formats.
Backup Import (Source-Specific)
apps/mobile/src/lib/backupImport/import/tackbok.ts, gratitudeApp.ts, presently.ts, helpers.ts
Three import handlers: Tackbok ZIP (validates manifest, upserts tags/prompts, imports entries/assets/profile), Gratitude App ZIP (builds portable payload, translates records), Presently CSV (parses CSV, validates headers, detects duplicates by date+content). Shared helpers for file picking and totals.
Backup Import Module Exports
apps/mobile/src/lib/backupImport/index.ts
Public re-export index centralizing all import APIs, types, progress utilities, and phase ordering.
Settings Store Extensions
apps/mobile/src/lib/settings/store.ts
Added persisted profile fields (profileName, profileEmail, profileImageUri) with store actions and null-coalescing trimming.
Import UI Modals
apps/mobile/src/screens/settings/SettingsImportModeModal.tsx, SettingsImportProgressModal.tsx, SettingsImportSummaryModal.tsx
Three modals: mode selector (skip/overwrite), live progress tracker with phase labels and media counts, and completion summary with import statistics.
Backup Restore Section UI
apps/mobile/src/screens/settings/sections/BackupRestoreSection.tsx
Refactored from CSV to ZIP workflows: replaced exportToCSV with exportToBackupZip, added Tackbok/Gratitude ZIP import selection, Presently CSV import picker, shared import orchestration with progress/summary lifecycle.
Translations
apps/mobile/src/lib/i18n/translations/{ar,en,he,zh-CN,zh-TW}.ts
Removed deprecated translation keys 'Preparing import' and Warnings across all language files.
Legacy Removal
apps/mobile/src/lib/backup.ts
Deleted entire CSV-based backup module (550 LOC): exportToCSV, pickCSVFile, importFromCSV, importFromPresentlyCSV.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant UI as BackupRestoreSection
    participant Modal as ImportModeModal
    participant Core as Import Core<br/>(tackbok/gratitude)
    participant DB as Database
    participant Files as File System
    
    User->>UI: Tap "Import from ZIP"
    UI->>UI: pickZipImportFile()
    Note over UI: User selects ZIP file
    UI->>Modal: Show import mode selection
    User->>Modal: Select "skip" or "overwrite"
    Modal->>UI: onSelectMode(mode)
    UI->>Core: importFromTackbokBackup(uri, mode, onProgress)
    Core->>Core: isZipFile() & loadZipFromUri()
    Core->>Core: Validate manifest
    Core->>DB: Begin transaction
    Core->>DB: Fetch existing entries
    Core->>DB: Upsert tags & prompts
    loop For each entry asset
        Core->>Files: Read from ZIP
        Core->>Files: Write to app documents
        Core->>DB: Create asset record
    end
    Core->>DB: Import/upsert entries
    Core->>DB: Commit transaction
    Core->>DB: Apply imported profile
    Core->>UI: reportImportProgress(...summary)
    UI->>UI: Display summary modal
    User->>UI: Tap "Done"
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • #32: Modifies the same i18n translation files with backup/import-related key changes.
  • #26: Edits the removed apps/mobile/src/lib/backup.ts CSV import/export module.
  • #18: Adds Presently CSV import support to the legacy backup system, now replaced by ZIP-based approach.

Poem

🐰 ZIP files now bundle our memories tight,
From Gratitude App and backups, we extract with might.
Progress bars twinkle as imports dance through the night,
Profile photos and voice memos—all tucked in just right! 📸🎵

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 48.76% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately reflects the main objective: upgrading the import/export system to support media and third-party apps, which are the core themes across all file changes.
Linked Issues check ✅ Passed The code fully implements requirements from issue #30: ZIP-based export with media support, Gratitude App import, graceful missing-file handling, mode selection UI, progress tracking, and summary reporting.
Out of Scope Changes check ✅ Passed All changes are in scope. The PR replaces CSV with ZIP, adds Gratitude App and Presently imports, creates supporting infrastructure (ZIP codecs, icon components), updates UI modals, and removes obsolete CSV functions.

✏️ 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 feat.update-import-export

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

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

🧹 Nitpick comments (4)
apps/mobile/src/lib/zip/uzipLib/zip-container.ts (1)

70-86: ZIP64 values are truncated to 32 bits.

The ZIP64 extra field stores 64-bit values, but readUint only reads 32 bits. The code correctly advances extraOffset by 8 bytes but discards the high 32 bits of each value. For backup archives exceeding ~4GB (total size or individual file), this will silently corrupt offsets/sizes.

For a mobile journaling app this is unlikely to be hit soon, but if you intend to support large media libraries, consider adding a readUint64 helper or at minimum validating that the high 32 bits are zero.

Example validation approach
       if (id === 1) {
         if (uncompressedSize === 0xffffffff) {
-          uncompressedSize = readUint(data, offset + extraOffset);
+          const low = readUint(data, offset + extraOffset);
+          const high = readUint(data, offset + extraOffset + 4);
+          if (high !== 0) {
+            throw new Error('ZIP64 archives with files > 4GB are not supported');
+          }
+          uncompressedSize = low;
           extraOffset += 8;
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/mobile/src/lib/zip/uzipLib/zip-container.ts` around lines 70 - 86, The
ZIP64 extra-field handling is incorrectly reading 64-bit values with readUint
(32-bit) and discarding the high 32 bits; update the logic inside the id === 1
branch (where uncompressedSize, compressedSize, recordOffset and extraOffset are
adjusted) to read full 64-bit integers — implement or call a readUint64 helper
(or readHighLow via two readUint calls) and assign the full 64-bit value to
uncompressedSize/compressedSize/recordOffset, advancing extraOffset by 8 each
time; alternatively, if you cannot support >32-bit sizes yet, validate the high
32 bits are zero after reading two 32-bit words and throw or return an error to
avoid silent truncation.
apps/mobile/src/lib/zip/uzipLib/byte-utils.ts (1)

110-130: Consider clearer UTF-8 range checks.

The bitmask expressions like (code & (0xffffffff - (1 << 7) + 1)) === 0 are correct but obscure. Standard UTF-8 implementations typically use direct comparisons for readability:

Clearer alternative
-    if ((code & (0xffffffff - (1 << 7) + 1)) === 0) {
+    if (code < 0x80) {
       buffer[offset + written] = code;
       written += 1;
-    } else if ((code & (0xffffffff - (1 << 11) + 1)) === 0) {
+    } else if (code < 0x800) {
       buffer[offset + written] = 192 | (code >> 6);
       // ...
-    } else if ((code & (0xffffffff - (1 << 16) + 1)) === 0) {
+    } else if (code < 0x10000) {
       // ...
-    } else if ((code & (0xffffffff - (1 << 21) + 1)) === 0) {
+    } else if (code < 0x110000) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/mobile/src/lib/zip/uzipLib/byte-utils.ts` around lines 110 - 130,
Replace the obscure bitmask checks for UTF-8 branch selection with direct,
readable range comparisons: use if (code <= 0x7F) { ... } else if (code <=
0x7FF) { ... } else if (code <= 0xFFFF) { ... } else if (code <= 0x10FFFF) { ...
} in the same block that writes to buffer using buffer[offset + written] and
increments written; also add an explicit validation that code is non‑negative,
<= 0x10FFFF and not a UTF‑16 surrogate (code < 0xD800 || code > 0xDFFF) before
encoding, and keep the final throw for unsupported code points.
apps/mobile/src/lib/backupImport/archiveUtils.ts (2)

255-263: Make asset type handling explicit instead of defaulting to audio.

Line 261 and Line 273 treat every non-image type as voice memo. That can silently mis-handle unknown/future AssetType values.

♻️ Proposed fix
 export function createArchiveAssetPath(type: Asset['type'], relativeUri: string): string {
   const fileName = relativeUri.split('/').pop();
   if (!fileName) {
     throw new Error('Asset URI is invalid');
   }
 
-  const dirName = type === AssetType.IMAGE ? 'photos' : 'voice-memos';
+  const dirName =
+    type === AssetType.IMAGE
+      ? 'photos'
+      : type === AssetType.AUDIO
+        ? 'voice-memos'
+        : null;
+  if (!dirName) {
+    throw new Error(`Unsupported asset type: ${String(type)}`);
+  }
   return `media/${dirName}/${fileName}`;
 }
@@
 export function assetFileExists(asset: Asset): boolean {
   if (asset.type === AssetType.IMAGE) {
     return photoFileExists(asset.uri);
   }
-
-  return voiceMemoFileExists(asset.uri);
+  if (asset.type === AssetType.AUDIO) {
+    return voiceMemoFileExists(asset.uri);
+  }
+  return false;
 }

Also applies to: 268-274

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/mobile/src/lib/backupImport/archiveUtils.ts` around lines 255 - 263, The
function createArchiveAssetPath treats any non-IMAGE AssetType as 'voice-memos',
which can silently mis-handle unknown/future AssetType values; update it to
handle AssetType explicitly (e.g., use a switch or if/else branches over
AssetType.IMAGE, AssetType.AUDIO, etc.) mapping each known type to the correct
directory name and throw an Error for unrecognized AssetType values so unknown
enums don't default to audio; reference createArchiveAssetPath and AssetType
when making this change.

190-203: Consolidate ZIP validation and parse flow to avoid repeated large reads.

Line 192 and Line 201 each load full bytes; if both checks run in sequence, large backups are read twice in memory. Prefer one byte-read path with shared signature validation.

♻️ Proposed refactor
+function hasZipSignature(bytes: Uint8Array): boolean {
+  if (bytes.length < 4) return false;
+  const b0 = bytes[0];
+  const b1 = bytes[1];
+  const b2 = bytes[2];
+  const b3 = bytes[3];
+  // Local file header, empty archive, or spanning/split signatures.
+  return (
+    (b0 === 0x50 && b1 === 0x4b && b2 === 0x03 && b3 === 0x04) ||
+    (b0 === 0x50 && b1 === 0x4b && b2 === 0x05 && b3 === 0x06) ||
+    (b0 === 0x50 && b1 === 0x4b && b2 === 0x07 && b3 === 0x08)
+  );
+}
+
 export async function loadZipFromUri(uri: string): Promise<ZipArchive> {
   const file = new File(uri);
   const bytes = await file.bytes();
+  if (!hasZipSignature(bytes)) {
+    throw new Error('Selected file is not a ZIP archive');
+  }
   return parseZipArchive(bytes);
 }
 
 export async function isZipFile(uri: string): Promise<boolean> {
   const file = new File(uri);
   const bytes = await file.bytes();
-  return bytes.length >= 4 && bytes[0] === 0x50 && bytes[1] === 0x4b;
+  return hasZipSignature(bytes);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/mobile/src/lib/backupImport/archiveUtils.ts` around lines 190 - 203,
Both loadZipFromUri and isZipFile read the full file bytes separately causing
duplicate large reads; change the flow to read bytes only once: create a
single-path that calls File(uri).bytes() once, perform the signature check
(check bytes[0] === 0x50 && bytes[1] === 0x4b) on the returned Uint8Array, and
then pass the same bytes to parseZipArchive; either modify loadZipFromUri to do
the validation before calling parseZipArchive or refactor isZipFile to accept
bytes (e.g., isZipBytes) so callers reuse the already-read bytes from
File.uri.bytes() instead of re-reading.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/mobile/src/components/ImportSourceIcons.tsx`:
- Around line 29-34: PresentlyLogoIcon's Path uses transform="scale(0.11)" while
the Svg viewBox stays "0 0 24 24", causing the scaled geometry to be clipped;
update the component (PresentlyLogoIcon) so the SVG coordinate system matches
the scaled path: either remove the Path's transform and normalize the path
coordinates to fit a 24x24 viewBox, or keep the original path coordinates and
change the Svg viewBox to the path's full bounds (e.g., scale 0.11 → multiply
viewBox width/height by ~9.09) or compute an appropriate viewBox that
accommodates transform, and ensure width/height and preserveAspectRatio remain
correct; adjust the Path transform or Svg viewBox accordingly to eliminate
clipping.

In `@apps/mobile/src/lib/backupExport/tackbok.ts`:
- Around line 29-39: exportToBackupZip currently always includes media; change
its signature to accept an optional boolean parameter (e.g., includeMedia =
true) so callers (UI/export button) can request data-only exports. Inside
exportToBackupZip use the includeMedia flag to skip adding entry asset files and
the profileImageUri to the archive (when false) — specifically avoid processing
each entry's assets when iterating over allEntries and skip adding
profileImageUri; keep the existing default true for backward compatibility and
update any callers to pass false for data-only backups (also apply the same
includeMedia check to the logic currently between lines 51-87 that adds media
files).
- Around line 65-87: The code currently checks existence but still calls await
sourceFile.bytes() and await profileFile.bytes() which can throw and abort the
whole backup; wrap each read-and-add operation in a try/catch around the
zip.addBytes calls (for the loop over portableEntries -> entry.assets and for
the profileImageUri branch) so that if getRelativeAssetFile(...).bytes() throws
you log or otherwise handle the error and continue to the next asset instead of
throwing; reference the portableEntries loop, asset.path,
getRelativeAssetFile(), zip.addBytes(...), profileImageUri, profileFile and
profile.imagePath when applying the try/catch and skipping unreadable files.

In `@apps/mobile/src/lib/backupImport/archiveUtils.ts`:
- Around line 229-231: The split/map/filter chain uses raw split tokens so
entries like "id1, id2" fail to match; update the map callback to trim each
token before lookup (e.g., replace tagMap.get(tagId) with
tagMap.get(tagId.trim())) and preserve the existing type guard filter ((title):
title is string => !!title) so empty/whitespace-only tokens are dropped;
reference the tagMap variable and the mapping expression in archiveUtils.ts.

In `@apps/mobile/src/lib/backupImport/import/presently.ts`:
- Around line 159-166: The duplicate check builds dateMs using new
Date(`${entryDateStr}T00:00:00`).getTime(), which is parsed in local time and
can vary across timezones; change this to compute a UTC timestamp explicitly
(e.g., parse entryDateStr into year/month/day and use Date.UTC(year, month-1,
day)) so dateMs is consistent across timezones, then use that UTC dateMs in the
query that compares entries.created_at (and ensure created_at is
stored/normalized as UTC milliseconds to match the new comparison).

In `@apps/mobile/src/lib/backupImport/import/tackbok.ts`:
- Around line 100-115: The current flow lets importPortableEntries bubble
readSafeZipBytes/writeImportedPhoto errors so one bad asset aborts the whole
restore; fix by handling IO errors per-asset inside importPortableEntries (the
function invoked here), wrapping calls to readSafeZipBytes and
writeImportedPhoto in try/catch, logging the error and calling
cleanupImportedFiles for that asset, marking the entry/asset as skipped
(optionally returning/skipping it) and continuing to the next entry instead of
rethrowing; keep the outer transaction behavior but ensure importPortableEntries
returns a summary of skipped failures so callers (like this tackbok importer and
the Gratitude importer) can report them rather than letting a single corrupt
media file rollback the entire import.

In `@apps/mobile/src/lib/zip/uzipLib/crc32.ts`:
- Around line 33-35: The computeCrc32 function currently returns a signed 32-bit
number due to JS bitwise semantics; update computeCrc32 (which calls
updateCrc32) to normalize the result to an unsigned 32-bit value by applying >>>
0 to the final expression (i.e., (updateCrc32(0xffffffff, buffer, offset,
length) ^ 0xffffffff) >>> 0) so CRCs are produced as unsigned uint32s per ZIP
spec and remain consistent when compared or passed to other functions like
writeUint.

In `@apps/mobile/src/lib/zip/uzipLib/deflate-codec.ts`:
- Around line 996-1006: The unrolled copy in deflate-codec.ts using variables
offset, end, distance, and output can write past end when (end - offset) < 4;
change the unrolled loop to only run while (offset + 4 <= end) (or equivalent
check) to guarantee four-byte batches fit, then handle the remaining 0–3 bytes
with a separate safe loop that copies one byte at a time while (offset < end);
ensure you update the section around the existing while loop and leave offset =
end at the end.

In `@apps/mobile/src/screens/settings/sections/BackupRestoreSection.tsx`:
- Around line 171-185: The ZIP import flow in handleRunPendingImport only passes
an ImportMode (duplicate-entry policy) so there is no include/exclude-media
option for ZIP restores; update the flow to accept and forward a media-inclusion
flag: extend pendingImportSelection to carry a media boolean (e.g.,
includeMedia), update any callers that set pendingImportSelection, change
handleRunPendingImport to extract includeMedia and call runImportFlow with a
runner that invokes importFromTackbokBackup or importFromGratitudeAppBackup as
runImport(selection.uri, mode, includeMedia, onProgress), and ensure
runImportFlow and the underlying
importFromTackbokBackup/importFromGratitudeAppBackup signatures accept and
propagate the includeMedia flag so users can restore entries without
photos/voice memos.

In `@apps/mobile/src/screens/settings/SettingsImportProgressModal.tsx`:
- Around line 57-59: The modal is duplicating the "no journal entries" message
when progress.phase === 'entries' and totalEntries === 0; update
SettingsImportProgressModal to suppress the secondary callout rather than
changing copy: either adjust getCurrentDetail(progress, t) to return an empty
string/undefined in the zero-entries case or wrap the extra callout rendering
(the block that appears around lines 89-94) with a condition that checks
!(progress.phase === 'entries' && totalEntries === 0). Ensure you reference
progress.phase, totalEntries, and getCurrentDetail so the UI only shows the
message once.

---

Nitpick comments:
In `@apps/mobile/src/lib/backupImport/archiveUtils.ts`:
- Around line 255-263: The function createArchiveAssetPath treats any non-IMAGE
AssetType as 'voice-memos', which can silently mis-handle unknown/future
AssetType values; update it to handle AssetType explicitly (e.g., use a switch
or if/else branches over AssetType.IMAGE, AssetType.AUDIO, etc.) mapping each
known type to the correct directory name and throw an Error for unrecognized
AssetType values so unknown enums don't default to audio; reference
createArchiveAssetPath and AssetType when making this change.
- Around line 190-203: Both loadZipFromUri and isZipFile read the full file
bytes separately causing duplicate large reads; change the flow to read bytes
only once: create a single-path that calls File(uri).bytes() once, perform the
signature check (check bytes[0] === 0x50 && bytes[1] === 0x4b) on the returned
Uint8Array, and then pass the same bytes to parseZipArchive; either modify
loadZipFromUri to do the validation before calling parseZipArchive or refactor
isZipFile to accept bytes (e.g., isZipBytes) so callers reuse the already-read
bytes from File.uri.bytes() instead of re-reading.

In `@apps/mobile/src/lib/zip/uzipLib/byte-utils.ts`:
- Around line 110-130: Replace the obscure bitmask checks for UTF-8 branch
selection with direct, readable range comparisons: use if (code <= 0x7F) { ... }
else if (code <= 0x7FF) { ... } else if (code <= 0xFFFF) { ... } else if (code
<= 0x10FFFF) { ... } in the same block that writes to buffer using buffer[offset
+ written] and increments written; also add an explicit validation that code is
non‑negative, <= 0x10FFFF and not a UTF‑16 surrogate (code < 0xD800 || code >
0xDFFF) before encoding, and keep the final throw for unsupported code points.

In `@apps/mobile/src/lib/zip/uzipLib/zip-container.ts`:
- Around line 70-86: The ZIP64 extra-field handling is incorrectly reading
64-bit values with readUint (32-bit) and discarding the high 32 bits; update the
logic inside the id === 1 branch (where uncompressedSize, compressedSize,
recordOffset and extraOffset are adjusted) to read full 64-bit integers —
implement or call a readUint64 helper (or readHighLow via two readUint calls)
and assign the full 64-bit value to
uncompressedSize/compressedSize/recordOffset, advancing extraOffset by 8 each
time; alternatively, if you cannot support >32-bit sizes yet, validate the high
32 bits are zero after reading two 32-bit words and throw or return an error to
avoid silent truncation.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: fdacce10-df6a-419a-a3a7-dde3051be8d4

📥 Commits

Reviewing files that changed from the base of the PR and between 629f328 and 1366531.

📒 Files selected for processing (32)
  • apps/mobile/src/components/ImportSourceIcons.tsx
  • apps/mobile/src/lib/backup.ts
  • apps/mobile/src/lib/backupExport/index.ts
  • apps/mobile/src/lib/backupExport/portable.ts
  • apps/mobile/src/lib/backupExport/tackbok.ts
  • apps/mobile/src/lib/backupImport/archiveUtils.ts
  • apps/mobile/src/lib/backupImport/import/gratitudeApp.ts
  • apps/mobile/src/lib/backupImport/import/helpers.ts
  • apps/mobile/src/lib/backupImport/import/presently.ts
  • apps/mobile/src/lib/backupImport/import/tackbok.ts
  • apps/mobile/src/lib/backupImport/index.ts
  • apps/mobile/src/lib/backupImport/portable.ts
  • apps/mobile/src/lib/backupImport/progress.ts
  • apps/mobile/src/lib/backupImport/types.ts
  • apps/mobile/src/lib/i18n/translations/ar.ts
  • apps/mobile/src/lib/i18n/translations/en.ts
  • apps/mobile/src/lib/i18n/translations/he.ts
  • apps/mobile/src/lib/i18n/translations/zh-CN.ts
  • apps/mobile/src/lib/i18n/translations/zh-TW.ts
  • apps/mobile/src/lib/settings/store.ts
  • apps/mobile/src/lib/zip/archive.ts
  • apps/mobile/src/lib/zip/index.ts
  • apps/mobile/src/lib/zip/uzipLib/byte-utils.ts
  • apps/mobile/src/lib/zip/uzipLib/crc32.ts
  • apps/mobile/src/lib/zip/uzipLib/deflate-codec.ts
  • apps/mobile/src/lib/zip/uzipLib/index.ts
  • apps/mobile/src/lib/zip/uzipLib/types.ts
  • apps/mobile/src/lib/zip/uzipLib/zip-container.ts
  • apps/mobile/src/screens/settings/SettingsImportModeModal.tsx
  • apps/mobile/src/screens/settings/SettingsImportProgressModal.tsx
  • apps/mobile/src/screens/settings/SettingsImportSummaryModal.tsx
  • apps/mobile/src/screens/settings/sections/BackupRestoreSection.tsx
💤 Files with no reviewable changes (6)
  • apps/mobile/src/lib/i18n/translations/en.ts
  • apps/mobile/src/lib/i18n/translations/he.ts
  • apps/mobile/src/lib/i18n/translations/zh-CN.ts
  • apps/mobile/src/lib/i18n/translations/ar.ts
  • apps/mobile/src/lib/i18n/translations/zh-TW.ts
  • apps/mobile/src/lib/backup.ts

Comment on lines +29 to +34
<Svg ref={ref} width={size} height={size} viewBox="0 0 24 24" fill="none" {...props}>
<Path
d="m183.2 242.7c0.83 1.35 1.92 0.33 1.5-0.99-1.82-5.77-4.37-11.26-7.45-16.59-1.88-10.32-1.43-21.27-0.83-34.33 0.08-1.61 0-4.36 1.45-4.04 1.67 0.4 5.42 4.53 10.15 4.67 5.24 0.15 11.06-4.14 13.7-8.81 4.64-8.28 6.84-9.15 8.05-13.19 1.85-6.56-0.86-13.72-3.07-21.09-3.02-10.25-21-16.38-23.77-21.51-1.71-3.35-0.81-5.72-2.44-6.36-1.16-0.46-1.2 2.99-3.07 5.32-3.08 3.92-10.65 4.82-21.28 11.63-11.84 7.2-12.31 18.69-10.67 28.19-3.23-9.54-7.88-18.32-13-30.9-1-2.55-0.49-3.75 1.33-5.96 4.13-4.97 7.52-6.62 10.12-11.76 3.7 4.61 4.57 9.79 13.83 9.49 6.36-0.22 21.14-9.86 28.07-21.47 4.4-7.02 3.56-15.25 1.26-21.17-2.85-7.51-4.01-9.62-4.69-20.81-0.17-2.26-1.64-1.71-2.09-0.89-1.51 2.22-6.58 5.48-11.92 5.67-7.3 0.34-9.53-0.33-15.5 0.3-2.61-0.67-4.95 0.3-6.96 0.87-7.85 1.85-10.53 6.3-12.54 10.17-2.74 5.51-8.31 6.61-9.48 16.31-0.68 5.73 0.93 14.65 5.3 19.66 2.78 2.63 8.74 1.19 10.45 1.35 2.51 0.16-4.54 8.41-8.33 12.34-3.39-5.45-20.15-20.05-26.19-35.9-2.17-7.2-5.45-16.55-2.76-25.51 2.56-1.18 6.45 1.96 10.93 0.47 3.43-1.19 8.56-7.32 9.82-10.88 1.72-6.03 3.84-16.92-0.28-21.88-2.72-2.73-4.12-5.87-6.12-8.87-3.43-5.17-8.62-8.87-10.52-12.36-1.88-3.11-1.86-5.04-2.38-6.3-0.58-1.28-1.27-0.77-1.74-0.22-1.36 1.55-2.01 4.89-4.8 8.15-1.7 1.85-3.88 2.16-8.6 9.28-2.63 4.44-5.56 6.09-7.64 11.17-2.05 4.9-3.85 13.75-4.43 15.64-0.65 2.36 2.22 6.74 5.61 12.19 3.01 5.29 9.97 6.76 13.96 4.84 1.06-0.51 2.38-0.86 3.6-1.14-2.01 7.98 0.15 15.23 3.17 25.76 1.57 5.58 2.47 7.54 5.31 12.06-4.33-2.03-8.36-2.25-12.12-3.04 2.34-5.51 3.82-8.86 1.84-15.66-2.51-7.06-10.2-14.98-20.8-15.55-4.34-0.15-11.09 1.77-14.39 2.92-5.69 2.2-5.99 6.54-9.77 9.5-4.55 3.5-10.6 5.38-14.04 7.31-2.82 1.77-1.82 2.88-0.16 3.12 3.88 0.89 5.61 4.46 6.81 9.92 1.48 4.81 4.12 12.13 8.29 15.81 3.51 2.23 10.94 5.23 20.97 5.13 6.94-0.11 11.63-3.54 13.7-5.09 5.26-4.27 5.87-9.77 5.83-14.59 6.38 0.86 13.5 1.67 17.66 6.72 3.84 5.22 11.41 11.92 15.3 17.9 2.34 3.53 2.84 7.28 5.5 13.01 4.21 9.56 8.62 20.59 12.1 29.66-7.49-1.97-11.99-2.62-18.91-3.97 4.01-0.78 3.87-4.72 2.41-9.69-2.29-6.47-11.36-11-22.66-15.06-5.39-1.76-8.04-0.05-12.09 4.01-7.33 7.44-11.65 12.72-17.61 20.92-0.87 0.78-2.38 1.69-1.02 2.81 4.63 1.89 14.9 15.44 19.2 26.01-1 0.85-2.15 0.78-1.12 1.82 4.01 2.59 3.95 6.9 4.71 13.43 0.79 5.13 3.09 14.21 6.66 18.98 5.72 6.98 9.27 10.18 19.33 10.51 5.94-0.75 8.98-2.16 16.19-2.27 7.86-0.26 13.91-0.64 17.56-12.52 7.4 1.4 16.45 3.66 21.54 7.46 1.62 2.25 2.53 5.46 4.03 7.89zm-5.72-12.78c-4.41-2.74-10.27-4.38-17.25-5.91 2.61-3.86-0.1-6.89-2.89-9.31-5.92-5.09-12.14-15.55-18.8-18.33-4.72-2.31-9.77-2.41-15.35-2.87 2.63-5.5 4.74-7.43 3.76-15.22-0.48-3.18-1.81-7.35-1.81-7.35 6.75 1.44 12.8 2.67 21.25 4.57 3.7 8.63 10.33 18.27 15.23 25.44 3.87 5.51 4.87 11.17 12.2 21.35 1.57 2.56 2.35 4.11 3.66 7.63zm-3.76-12.55c-3.65-5-5.25-12.8-10-19.62-3.69-5.6-7.84-10.51-9.37-14.16 4.11 4.71 7.42 5.61 12.61 4.31 2.88-0.97 5.44-2.52 6.97-1.1 0.09 4.26-1.86 16.06-0.21 30.57z"
fill={color}
transform="scale(0.11)"
/>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential clipping in PresentlyLogoIcon due to viewBox/scale mismatch.

On Line 29 and Line 33, viewBox="0 0 24 24" with transform="scale(0.11)" can clip the lower geometry (scaled path height exceeds 24).

💡 Suggested fix
-    <Svg ref={ref} width={size} height={size} viewBox="0 0 24 24" fill="none" {...props}>
+    <Svg ref={ref} width={size} height={size} viewBox="0 0 256 256" fill="none" {...props}>
       <Path
         d="m183.2 242.7c0.83 1.35 1.92 0.33 1.5-0.99-1.82-5.77-4.37-11.26-7.45-16.59-1.88-10.32-1.43-21.27-0.83-34.33 0.08-1.61 0-4.36 1.45-4.04 1.67 0.4 5.42 4.53 10.15 4.67 5.24 0.15 11.06-4.14 13.7-8.81 4.64-8.28 6.84-9.15 8.05-13.19 1.85-6.56-0.86-13.72-3.07-21.09-3.02-10.25-21-16.38-23.77-21.51-1.71-3.35-0.81-5.72-2.44-6.36-1.16-0.46-1.2 2.99-3.07 5.32-3.08 3.92-10.65 4.82-21.28 11.63-11.84 7.2-12.31 18.69-10.67 28.19-3.23-9.54-7.88-18.32-13-30.9-1-2.55-0.49-3.75 1.33-5.96 4.13-4.97 7.52-6.62 10.12-11.76 3.7 4.61 4.57 9.79 13.83 9.49 6.36-0.22 21.14-9.86 28.07-21.47 4.4-7.02 3.56-15.25 1.26-21.17-2.85-7.51-4.01-9.62-4.69-20.81-0.17-2.26-1.64-1.71-2.09-0.89-1.51 2.22-6.58 5.48-11.92 5.67-7.3 0.34-9.53-0.33-15.5 0.3-2.61-0.67-4.95 0.3-6.96 0.87-7.85 1.85-10.53 6.3-12.54 10.17-2.74 5.51-8.31 6.61-9.48 16.31-0.68 5.73 0.93 14.65 5.3 19.66 2.78 2.63 8.74 1.19 10.45 1.35 2.51 0.16-4.54 8.41-8.33 12.34-3.39-5.45-20.15-20.05-26.19-35.9-2.17-7.2-5.45-16.55-2.76-25.51 2.56-1.18 6.45 1.96 10.93 0.47 3.43-1.19 8.56-7.32 9.82-10.88 1.72-6.03 3.84-16.92-0.28-21.88-2.72-2.73-4.12-5.87-6.12-8.87-3.43-5.17-8.62-8.87-10.52-12.36-1.88-3.11-1.86-5.04-2.38-6.3-0.58-1.28-1.27-0.77-1.74-0.22-1.36 1.55-2.01 4.89-4.8 8.15-1.7 1.85-3.88 2.16-8.6 9.28-2.63 4.44-5.56 6.09-7.64 11.17-2.05 4.9-3.85 13.75-4.43 15.64-0.65 2.36 2.22 6.74 5.61 12.19 3.01 5.29 9.97 6.76 13.96 4.84 1.06-0.51 2.38-0.86 3.6-1.14-2.01 7.98 0.15 15.23 3.17 25.76 1.57 5.58 2.47 7.54 5.31 12.06-4.33-2.03-8.36-2.25-12.12-3.04 2.34-5.51 3.82-8.86 1.84-15.66-2.51-7.06-10.2-14.98-20.8-15.55-4.34-0.15-11.09 1.77-14.39 2.92-5.69 2.2-5.99 6.54-9.77 9.5-4.55 3.5-10.6 5.38-14.04 7.31-2.82 1.77-1.82 2.88-0.16 3.12 3.88 0.89 5.61 4.46 6.81 9.92 1.48 4.81 4.12 12.13 8.29 15.81 3.51 2.23 10.94 5.23 20.97 5.13 6.94-0.11 11.63-3.54 13.7-5.09 5.26-4.27 5.87-9.77 5.83-14.59 6.38 0.86 13.5 1.67 17.66 6.72 3.84 5.22 11.41 11.92 15.3 17.9 2.34 3.53 2.84 7.28 5.5 13.01 4.21 9.56 8.62 20.59 12.1 29.66-7.49-1.97-11.99-2.62-18.91-3.97 4.01-0.78 3.87-4.72 2.41-9.69-2.29-6.47-11.36-11-22.66-15.06-5.39-1.76-8.04-0.05-12.09 4.01-7.33 7.44-11.65 12.72-17.61 20.92-0.87 0.78-2.38 1.69-1.02 2.81 4.63 1.89 14.9 15.44 19.2 26.01-1 0.85-2.15 0.78-1.12 1.82 4.01 2.59 3.95 6.9 4.71 13.43 0.79 5.13 3.09 14.21 6.66 18.98 5.72 6.98 9.27 10.18 19.33 10.51 5.94-0.75 8.98-2.16 16.19-2.27 7.86-0.26 13.91-0.64 17.56-12.52 7.4 1.4 16.45 3.66 21.54 7.46 1.62 2.25 2.53 5.46 4.03 7.89zm-5.72-12.78c-4.41-2.74-10.27-4.38-17.25-5.91 2.61-3.86-0.1-6.89-2.89-9.31-5.92-5.09-12.14-15.55-18.8-18.33-4.72-2.31-9.77-2.41-15.35-2.87 2.63-5.5 4.74-7.43 3.76-15.22-0.48-3.18-1.81-7.35-1.81-7.35 6.75 1.44 12.8 2.67 21.25 4.57 3.7 8.63 10.33 18.27 15.23 25.44 3.87 5.51 4.87 11.17 12.2 21.35 1.57 2.56 2.35 4.11 3.66 7.63zm-3.76-12.55c-3.65-5-5.25-12.8-10-19.62-3.69-5.6-7.84-10.51-9.37-14.16 4.11 4.71 7.42 5.61 12.61 4.31 2.88-0.97 5.44-2.52 6.97-1.1 0.09 4.26-1.86 16.06-0.21 30.57z"
         fill={color}
-        transform="scale(0.11)"
       />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Svg ref={ref} width={size} height={size} viewBox="0 0 24 24" fill="none" {...props}>
<Path
d="m183.2 242.7c0.83 1.35 1.92 0.33 1.5-0.99-1.82-5.77-4.37-11.26-7.45-16.59-1.88-10.32-1.43-21.27-0.83-34.33 0.08-1.61 0-4.36 1.45-4.04 1.67 0.4 5.42 4.53 10.15 4.67 5.24 0.15 11.06-4.14 13.7-8.81 4.64-8.28 6.84-9.15 8.05-13.19 1.85-6.56-0.86-13.72-3.07-21.09-3.02-10.25-21-16.38-23.77-21.51-1.71-3.35-0.81-5.72-2.44-6.36-1.16-0.46-1.2 2.99-3.07 5.32-3.08 3.92-10.65 4.82-21.28 11.63-11.84 7.2-12.31 18.69-10.67 28.19-3.23-9.54-7.88-18.32-13-30.9-1-2.55-0.49-3.75 1.33-5.96 4.13-4.97 7.52-6.62 10.12-11.76 3.7 4.61 4.57 9.79 13.83 9.49 6.36-0.22 21.14-9.86 28.07-21.47 4.4-7.02 3.56-15.25 1.26-21.17-2.85-7.51-4.01-9.62-4.69-20.81-0.17-2.26-1.64-1.71-2.09-0.89-1.51 2.22-6.58 5.48-11.92 5.67-7.3 0.34-9.53-0.33-15.5 0.3-2.61-0.67-4.95 0.3-6.96 0.87-7.85 1.85-10.53 6.3-12.54 10.17-2.74 5.51-8.31 6.61-9.48 16.31-0.68 5.73 0.93 14.65 5.3 19.66 2.78 2.63 8.74 1.19 10.45 1.35 2.51 0.16-4.54 8.41-8.33 12.34-3.39-5.45-20.15-20.05-26.19-35.9-2.17-7.2-5.45-16.55-2.76-25.51 2.56-1.18 6.45 1.96 10.93 0.47 3.43-1.19 8.56-7.32 9.82-10.88 1.72-6.03 3.84-16.92-0.28-21.88-2.72-2.73-4.12-5.87-6.12-8.87-3.43-5.17-8.62-8.87-10.52-12.36-1.88-3.11-1.86-5.04-2.38-6.3-0.58-1.28-1.27-0.77-1.74-0.22-1.36 1.55-2.01 4.89-4.8 8.15-1.7 1.85-3.88 2.16-8.6 9.28-2.63 4.44-5.56 6.09-7.64 11.17-2.05 4.9-3.85 13.75-4.43 15.64-0.65 2.36 2.22 6.74 5.61 12.19 3.01 5.29 9.97 6.76 13.96 4.84 1.06-0.51 2.38-0.86 3.6-1.14-2.01 7.98 0.15 15.23 3.17 25.76 1.57 5.58 2.47 7.54 5.31 12.06-4.33-2.03-8.36-2.25-12.12-3.04 2.34-5.51 3.82-8.86 1.84-15.66-2.51-7.06-10.2-14.98-20.8-15.55-4.34-0.15-11.09 1.77-14.39 2.92-5.69 2.2-5.99 6.54-9.77 9.5-4.55 3.5-10.6 5.38-14.04 7.31-2.82 1.77-1.82 2.88-0.16 3.12 3.88 0.89 5.61 4.46 6.81 9.92 1.48 4.81 4.12 12.13 8.29 15.81 3.51 2.23 10.94 5.23 20.97 5.13 6.94-0.11 11.63-3.54 13.7-5.09 5.26-4.27 5.87-9.77 5.83-14.59 6.38 0.86 13.5 1.67 17.66 6.72 3.84 5.22 11.41 11.92 15.3 17.9 2.34 3.53 2.84 7.28 5.5 13.01 4.21 9.56 8.62 20.59 12.1 29.66-7.49-1.97-11.99-2.62-18.91-3.97 4.01-0.78 3.87-4.72 2.41-9.69-2.29-6.47-11.36-11-22.66-15.06-5.39-1.76-8.04-0.05-12.09 4.01-7.33 7.44-11.65 12.72-17.61 20.92-0.87 0.78-2.38 1.69-1.02 2.81 4.63 1.89 14.9 15.44 19.2 26.01-1 0.85-2.15 0.78-1.12 1.82 4.01 2.59 3.95 6.9 4.71 13.43 0.79 5.13 3.09 14.21 6.66 18.98 5.72 6.98 9.27 10.18 19.33 10.51 5.94-0.75 8.98-2.16 16.19-2.27 7.86-0.26 13.91-0.64 17.56-12.52 7.4 1.4 16.45 3.66 21.54 7.46 1.62 2.25 2.53 5.46 4.03 7.89zm-5.72-12.78c-4.41-2.74-10.27-4.38-17.25-5.91 2.61-3.86-0.1-6.89-2.89-9.31-5.92-5.09-12.14-15.55-18.8-18.33-4.72-2.31-9.77-2.41-15.35-2.87 2.63-5.5 4.74-7.43 3.76-15.22-0.48-3.18-1.81-7.35-1.81-7.35 6.75 1.44 12.8 2.67 21.25 4.57 3.7 8.63 10.33 18.27 15.23 25.44 3.87 5.51 4.87 11.17 12.2 21.35 1.57 2.56 2.35 4.11 3.66 7.63zm-3.76-12.55c-3.65-5-5.25-12.8-10-19.62-3.69-5.6-7.84-10.51-9.37-14.16 4.11 4.71 7.42 5.61 12.61 4.31 2.88-0.97 5.44-2.52 6.97-1.1 0.09 4.26-1.86 16.06-0.21 30.57z"
fill={color}
transform="scale(0.11)"
/>
<Svg ref={ref} width={size} height={size} viewBox="0 0 256 256" fill="none" {...props}>
<Path
d="m183.2 242.7c0.83 1.35 1.92 0.33 1.5-0.99-1.82-5.77-4.37-11.26-7.45-16.59-1.88-10.32-1.43-21.27-0.83-34.33 0.08-1.61 0-4.36 1.45-4.04 1.67 0.4 5.42 4.53 10.15 4.67 5.24 0.15 11.06-4.14 13.7-8.81 4.64-8.28 6.84-9.15 8.05-13.19 1.85-6.56-0.86-13.72-3.07-21.09-3.02-10.25-21-16.38-23.77-21.51-1.71-3.35-0.81-5.72-2.44-6.36-1.16-0.46-1.2 2.99-3.07 5.32-3.08 3.92-10.65 4.82-21.28 11.63-11.84 7.2-12.31 18.69-10.67 28.19-3.23-9.54-7.88-18.32-13-30.9-1-2.55-0.49-3.75 1.33-5.96 4.13-4.97 7.52-6.62 10.12-11.76 3.7 4.61 4.57 9.79 13.83 9.49 6.36-0.22 21.14-9.86 28.07-21.47 4.4-7.02 3.56-15.25 1.26-21.17-2.85-7.51-4.01-9.62-4.69-20.81-0.17-2.26-1.64-1.71-2.09-0.89-1.51 2.22-6.58 5.48-11.92 5.67-7.3 0.34-9.53-0.33-15.5 0.3-2.61-0.67-4.95 0.3-6.96 0.87-7.85 1.85-10.53 6.3-12.54 10.17-2.74 5.51-8.31 6.61-9.48 16.31-0.68 5.73 0.93 14.65 5.3 19.66 2.78 2.63 8.74 1.19 10.45 1.35 2.51 0.16-4.54 8.41-8.33 12.34-3.39-5.45-20.15-20.05-26.19-35.9-2.17-7.2-5.45-16.55-2.76-25.51 2.56-1.18 6.45 1.96 10.93 0.47 3.43-1.19 8.56-7.32 9.82-10.88 1.72-6.03 3.84-16.92-0.28-21.88-2.72-2.73-4.12-5.87-6.12-8.87-3.43-5.17-8.62-8.87-10.52-12.36-1.88-3.11-1.86-5.04-2.38-6.3-0.58-1.28-1.27-0.77-1.74-0.22-1.36 1.55-2.01 4.89-4.8 8.15-1.7 1.85-3.88 2.16-8.6 9.28-2.63 4.44-5.56 6.09-7.64 11.17-2.05 4.9-3.85 13.75-4.43 15.64-0.65 2.36 2.22 6.74 5.61 12.19 3.01 5.29 9.97 6.76 13.96 4.84 1.06-0.51 2.38-0.86 3.6-1.14-2.01 7.98 0.15 15.23 3.17 25.76 1.57 5.58 2.47 7.54 5.31 12.06-4.33-2.03-8.36-2.25-12.12-3.04 2.34-5.51 3.82-8.86 1.84-15.66-2.51-7.06-10.2-14.98-20.8-15.55-4.34-0.15-11.09 1.77-14.39 2.92-5.69 2.2-5.99 6.54-9.77 9.5-4.55 3.5-10.6 5.38-14.04 7.31-2.82 1.77-1.82 2.88-0.16 3.12 3.88 0.89 5.61 4.46 6.81 9.92 1.48 4.81 4.12 12.13 8.29 15.81 3.51 2.23 10.94 5.23 20.97 5.13 6.94-0.11 11.63-3.54 13.7-5.09 5.26-4.27 5.87-9.77 5.83-14.59 6.38 0.86 13.5 1.67 17.66 6.72 3.84 5.22 11.41 11.92 15.3 17.9 2.34 3.53 2.84 7.28 5.5 13.01 4.21 9.56 8.62 20.59 12.1 29.66-7.49-1.97-11.99-2.62-18.91-3.97 4.01-0.78 3.87-4.72 2.41-9.69-2.29-6.47-11.36-11-22.66-15.06-5.39-1.76-8.04-0.05-12.09 4.01-7.33 7.44-11.65 12.72-17.61 20.92-0.87 0.78-2.38 1.69-1.02 2.81 4.63 1.89 14.9 15.44 19.2 26.01-1 0.85-2.15 0.78-1.12 1.82 4.01 2.59 3.95 6.9 4.71 13.43 0.79 5.13 3.09 14.21 6.66 18.98 5.72 6.98 9.27 10.18 19.33 10.51 5.94-0.75 8.98-2.16 16.19-2.27 7.86-0.26 13.91-0.64 17.56-12.52 7.4 1.4 16.45 3.66 21.54 7.46 1.62 2.25 2.53 5.46 4.03 7.89zm-5.72-12.78c-4.41-2.74-10.27-4.38-17.25-5.91 2.61-3.86-0.1-6.89-2.89-9.31-5.92-5.09-12.14-15.55-18.8-18.33-4.72-2.31-9.77-2.41-15.35-2.87 2.63-5.5 4.74-7.43 3.76-15.22-0.48-3.18-1.81-7.35-1.81-7.35 6.75 1.44 12.8 2.67 21.25 4.57 3.7 8.63 10.33 18.27 15.23 25.44 3.87 5.51 4.87 11.17 12.2 21.35 1.57 2.56 2.35 4.11 3.66 7.63zm-3.76-12.55c-3.65-5-5.25-12.8-10-19.62-3.69-5.6-7.84-10.51-9.37-14.16 4.11 4.71 7.42 5.61 12.61 4.31 2.88-0.97 5.44-2.52 6.97-1.1 0.09 4.26-1.86 16.06-0.21 30.57z"
fill={color}
/>
</Svg>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/mobile/src/components/ImportSourceIcons.tsx` around lines 29 - 34,
PresentlyLogoIcon's Path uses transform="scale(0.11)" while the Svg viewBox
stays "0 0 24 24", causing the scaled geometry to be clipped; update the
component (PresentlyLogoIcon) so the SVG coordinate system matches the scaled
path: either remove the Path's transform and normalize the path coordinates to
fit a 24x24 viewBox, or keep the original path coordinates and change the Svg
viewBox to the path's full bounds (e.g., scale 0.11 → multiply viewBox
width/height by ~9.09) or compute an appropriate viewBox that accommodates
transform, and ensure width/height and preserveAspectRatio remain correct;
adjust the Path transform or Svg viewBox accordingly to eliminate clipping.

Comment on lines +29 to +39
export async function exportToBackupZip(): Promise<void> {
const [allEntries, allTags, allPrompts] = await Promise.all([
db.select().from(entries).orderBy(desc(entries.created_at)),
db.select().from(tags),
db.select().from(customPrompts),
]);
const settings = useSettingsStore.getState();
const profileName = normalizeOptionalText(settings.profileName);
const profileEmail = normalizeOptionalText(settings.profileEmail);
const profileImageUri = normalizeOptionalText(settings.profileImageUri);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

exportToBackupZip() always forces media into the archive.

Issue #30 requires a user choice to export with or without media, but this API has no options and unconditionally adds every entry asset plus the profile image when present. As written, the UI cannot offer a data-only backup.

Also applies to: 51-87

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/mobile/src/lib/backupExport/tackbok.ts` around lines 29 - 39,
exportToBackupZip currently always includes media; change its signature to
accept an optional boolean parameter (e.g., includeMedia = true) so callers
(UI/export button) can request data-only exports. Inside exportToBackupZip use
the includeMedia flag to skip adding entry asset files and the profileImageUri
to the archive (when false) — specifically avoid processing each entry's assets
when iterating over allEntries and skip adding profileImageUri; keep the
existing default true for backward compatibility and update any callers to pass
false for data-only backups (also apply the same includeMedia check to the logic
currently between lines 51-87 that adds media files).

Comment on lines +65 to +87
for (const entry of portableEntries) {
for (const asset of entry.assets) {
const sourceFile = getRelativeAssetFile(
asset.path
.replace(`${BACKUP_MEDIA_PREFIX}/photos/`, `${PHOTOS_DIR_NAME}/`)
.replace(`${BACKUP_MEDIA_PREFIX}/voice-memos/`, `${VOICE_MEMOS_DIR_NAME}/`),
);
if (!sourceFile || !sourceFile.exists) {
continue;
}

zip.addBytes(asset.path, await sourceFile.bytes());
}
}

if (profileImageUri) {
const profileFile = getRelativeAssetFile(profileImageUri);
if (profileFile?.exists) {
const profileArchivePath = `${BACKUP_MEDIA_PREFIX}/profile/${profileImageUri.split('/').pop()}`;
zip.addBytes(profileArchivePath, await profileFile.bytes());
profile.imagePath = profileArchivePath;
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

One unreadable file currently cancels the whole backup.

The exists checks do not protect await sourceFile.bytes() / await profileFile.bytes() from failing later. If a file disappears between the check and the read, or is otherwise unreadable, the exception aborts the entire export instead of skipping that asset and continuing.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/mobile/src/lib/backupExport/tackbok.ts` around lines 65 - 87, The code
currently checks existence but still calls await sourceFile.bytes() and await
profileFile.bytes() which can throw and abort the whole backup; wrap each
read-and-add operation in a try/catch around the zip.addBytes calls (for the
loop over portableEntries -> entry.assets and for the profileImageUri branch) so
that if getRelativeAssetFile(...).bytes() throws you log or otherwise handle the
error and continue to the next asset instead of throwing; reference the
portableEntries loop, asset.path, getRelativeAssetFile(), zip.addBytes(...),
profileImageUri, profileFile and profile.imagePath when applying the try/catch
and skipping unreadable files.

Comment on lines +229 to +231
.split(',')
.map((tagId) => tagMap.get(tagId))
.filter((title): title is string => !!title);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Trim tag IDs before map lookup.

Line 230 currently looks up raw split tokens; inputs like "id1, id2" will miss id2. Trim each token before querying tagMap.

🩹 Proposed fix
   return tagIds
     .split(',')
-    .map((tagId) => tagMap.get(tagId))
+    .map((tagId) => tagMap.get(tagId.trim()))
     .filter((title): title is string => !!title);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/mobile/src/lib/backupImport/archiveUtils.ts` around lines 229 - 231, The
split/map/filter chain uses raw split tokens so entries like "id1, id2" fail to
match; update the map callback to trim each token before lookup (e.g., replace
tagMap.get(tagId) with tagMap.get(tagId.trim())) and preserve the existing type
guard filter ((title): title is string => !!title) so empty/whitespace-only
tokens are dropped; reference the tagMap variable and the mapping expression in
archiveUtils.ts.

Comment on lines +159 to +166
const dateMs = new Date(`${entryDateStr}T00:00:00`).getTime();
const existing = await tx
.select({ note_id: entries.note_id })
.from(entries)
.where(
and(eq(entries.created_at, dateMs), eq(entries.text_content, entryContent)),
)
.limit(1);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Date parsing uses local timezone, which may affect duplicate detection.

new Date('YYYY-MM-DDTHH:mm:ss') without a timezone suffix is parsed in local time. If a user imports the same CSV after changing timezones (or during DST transitions), the millisecond timestamp will differ, causing duplicate entries.

Consider using UTC explicitly for consistency:

Suggested fix
-      const dateMs = new Date(`${entryDateStr}T00:00:00`).getTime();
+      const dateMs = new Date(`${entryDateStr}T00:00:00Z`).getTime();

Note: This changes behavior—existing imports used local time. If you keep local time, document this clearly so users understand duplicate detection depends on their timezone at import time.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/mobile/src/lib/backupImport/import/presently.ts` around lines 159 - 166,
The duplicate check builds dateMs using new
Date(`${entryDateStr}T00:00:00`).getTime(), which is parsed in local time and
can vary across timezones; change this to compute a UTC timestamp explicitly
(e.g., parse entryDateStr into year/month/day and use Date.UTC(year, month-1,
day)) so dateMs is consistent across timezones, then use that UTC dateMs in the
query that compares entries.created_at (and ensure created_at is
stored/normalized as UTC milliseconds to match the new comparison).

Comment on lines +100 to +115
await importPortableEntries(
tx,
portableEntries,
existingNoteIds,
tagMap,
summary,
mode,
zip,
createdFiles,
'tackbok',
onProgress,
);
});
} catch (error) {
cleanupImportedFiles(createdFiles);
throw error;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

A single bad asset rolls back the entire restore.

importPortableEntries() throws on readSafeZipBytes() / writeImportedPhoto() failures, and this wrapper rethrows after cleanup. That makes one missing or corrupt media file enough to reject the whole backup instead of skipping the bad asset or entry and importing the rest. The same shared helper is used by the Gratitude importer.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/mobile/src/lib/backupImport/import/tackbok.ts` around lines 100 - 115,
The current flow lets importPortableEntries bubble
readSafeZipBytes/writeImportedPhoto errors so one bad asset aborts the whole
restore; fix by handling IO errors per-asset inside importPortableEntries (the
function invoked here), wrapping calls to readSafeZipBytes and
writeImportedPhoto in try/catch, logging the error and calling
cleanupImportedFiles for that asset, marking the entry/asset as skipped
(optionally returning/skipping it) and continuing to the next entry instead of
rethrowing; keep the outer transaction behavior but ensure importPortableEntries
returns a summary of skipped failures so callers (like this tackbok importer and
the Gratitude importer) can report them rather than letting a single corrupt
media file rollback the entire import.

Comment on lines +33 to +35
export function computeCrc32(buffer: Uint8Array, offset: number, length: number): number {
return updateCrc32(0xffffffff, buffer, offset, length) ^ 0xffffffff;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether computeCrc32 outputs are compared/written as unsigned.
rg -nP -C3 '\bcomputeCrc32\s*\(' apps/mobile/src/lib/zip
rg -nP -C3 'crc|CRC|getUint32|setUint32' apps/mobile/src/lib/zip/uzipLib

Repository: mehradotdev/tackbok

Length of output: 4677


🏁 Script executed:

# Examine writeUint implementation
rg -nP 'writeUint\s*\(' apps/mobile/src/lib/zip/uzipLib/byte-utils.ts -A 5

# Check for CRC comparisons or validations anywhere in the codebase
rg -nP 'crc\s*[=!<>]|crc\s+[=!<>]|compareCrc|validateCrc|verifyCrc' apps/mobile/src/lib/zip

# Look at the full byte-utils file to understand data types
cat apps/mobile/src/lib/zip/uzipLib/byte-utils.ts

Repository: mehradotdev/tackbok

Length of output: 5106


CRC32 should return unsigned uint32 for semantic correctness

Line 34 returns a signed int32 due to JavaScript's bitwise semantics. While the current writeUint implementation handles this correctly through byte masking, CRC32 values should be unsigned 32-bit per ZIP specification. Add >>> 0 to ensure the value is normalized to unsigned range for consistency and to prevent subtle bugs if the CRC is compared or passed to other functions.

Suggested fix
 export function computeCrc32(buffer: Uint8Array, offset: number, length: number): number {
-  return updateCrc32(0xffffffff, buffer, offset, length) ^ 0xffffffff;
+  return (updateCrc32(0xffffffff, buffer, offset, length) ^ 0xffffffff) >>> 0;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function computeCrc32(buffer: Uint8Array, offset: number, length: number): number {
return updateCrc32(0xffffffff, buffer, offset, length) ^ 0xffffffff;
}
export function computeCrc32(buffer: Uint8Array, offset: number, length: number): number {
return (updateCrc32(0xffffffff, buffer, offset, length) ^ 0xffffffff) >>> 0;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/mobile/src/lib/zip/uzipLib/crc32.ts` around lines 33 - 35, The
computeCrc32 function currently returns a signed 32-bit number due to JS bitwise
semantics; update computeCrc32 (which calls updateCrc32) to normalize the result
to an unsigned 32-bit value by applying >>> 0 to the final expression (i.e.,
(updateCrc32(0xffffffff, buffer, offset, length) ^ 0xffffffff) >>> 0) so CRCs
are produced as unsigned uint32s per ZIP spec and remain consistent when
compared or passed to other functions like writeUint.

Comment on lines +996 to +1006
while (offset < end) {
output![offset] = output![offset - distance];
offset += 1;
output![offset] = output![offset - distance];
offset += 1;
output![offset] = output![offset - distance];
offset += 1;
output![offset] = output![offset - distance];
offset += 1;
}
offset = end;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Unrolled copy loop may write past the intended end.

The loop copies 4 bytes per iteration but continues while offset < end. If (end - offset) before entering isn't divisible by 4, this writes 1–3 extra bytes past end. While ensureInflateBuffer provides slack, the extra writes corrupt those positions before offset = end truncates.

For correctness, either guard each write or use a safe unroll that handles the remainder:

Suggested fix
-      while (offset < end) {
+      while (offset + 3 < end) {
         output![offset] = output![offset - distance];
         offset += 1;
         output![offset] = output![offset - distance];
         offset += 1;
         output![offset] = output![offset - distance];
         offset += 1;
         output![offset] = output![offset - distance];
         offset += 1;
       }
-      offset = end;
+      while (offset < end) {
+        output![offset] = output![offset - distance];
+        offset += 1;
+      }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
while (offset < end) {
output![offset] = output![offset - distance];
offset += 1;
output![offset] = output![offset - distance];
offset += 1;
output![offset] = output![offset - distance];
offset += 1;
output![offset] = output![offset - distance];
offset += 1;
}
offset = end;
while (offset + 3 < end) {
output![offset] = output![offset - distance];
offset += 1;
output![offset] = output![offset - distance];
offset += 1;
output![offset] = output![offset - distance];
offset += 1;
output![offset] = output![offset - distance];
offset += 1;
}
while (offset < end) {
output![offset] = output![offset - distance];
offset += 1;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/mobile/src/lib/zip/uzipLib/deflate-codec.ts` around lines 996 - 1006,
The unrolled copy in deflate-codec.ts using variables offset, end, distance, and
output can write past end when (end - offset) < 4; change the unrolled loop to
only run while (offset + 4 <= end) (or equivalent check) to guarantee four-byte
batches fit, then handle the remaining 0–3 bytes with a separate safe loop that
copies one byte at a time while (offset < end); ensure you update the section
around the existing while loop and leave offset = end at the end.

Comment on lines +171 to +185
const handleRunPendingImport = useCallback(
async (mode: ImportMode) => {
const selection = pendingImportSelection;
if (!selection) return;

const source = selection.source;
setPendingImportSelection(null);
const runImport =
source === 'tackbok' ? importFromTackbokBackup : importFromGratitudeAppBackup;

const handleImportFromPresentlyCSV = useCallback(
() =>
handleImport(
() => setShowPresentlyImportConfirmDialog(false),
importFromPresentlyCSV,
),
[handleImport],
await runImportFlow(
source,
(onProgress) => runImport(selection.uri, mode, onProgress),
createImportProgressForSource(source, 'reading', 0.05),
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

ZIP imports still have no data-only mode.

This flow only collects ImportMode, and the shared importer consumes mode as the duplicate-entry policy. There is no separate include/exclude-media option here, so users still cannot restore entries without photos/voice memos, which misses Issue #30.

Also applies to: 288-292

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/mobile/src/screens/settings/sections/BackupRestoreSection.tsx` around
lines 171 - 185, The ZIP import flow in handleRunPendingImport only passes an
ImportMode (duplicate-entry policy) so there is no include/exclude-media option
for ZIP restores; update the flow to accept and forward a media-inclusion flag:
extend pendingImportSelection to carry a media boolean (e.g., includeMedia),
update any callers that set pendingImportSelection, change
handleRunPendingImport to extract includeMedia and call runImportFlow with a
runner that invokes importFromTackbokBackup or importFromGratitudeAppBackup as
runImport(selection.uri, mode, includeMedia, onProgress), and ensure
runImportFlow and the underlying
importFromTackbokBackup/importFromGratitudeAppBackup signatures accept and
propagate the includeMedia flag so users can restore entries without
photos/voice memos.

Comment on lines +57 to +59
<Text className="text-sm leading-5 text-muted-foreground">
{getCurrentDetail(progress, t)}
</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.

⚠️ Potential issue | 🟡 Minor

Avoid repeating the empty-backup message.

When progress.phase === 'entries' and totalEntries === 0, the subtitle already says that no journal entries were found. The extra callout repeats the same message, so the zero-entry case renders duplicate copy in the modal.

Also applies to: 89-94

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/mobile/src/screens/settings/SettingsImportProgressModal.tsx` around
lines 57 - 59, The modal is duplicating the "no journal entries" message when
progress.phase === 'entries' and totalEntries === 0; update
SettingsImportProgressModal to suppress the secondary callout rather than
changing copy: either adjust getCurrentDetail(progress, t) to return an empty
string/undefined in the zero-entries case or wrap the extra callout rendering
(the block that appears around lines 89-94) with a condition that checks
!(progress.phase === 'entries' && totalEntries === 0). Ensure you reference
progress.phase, totalEntries, and getCurrentDetail so the UI only shows the
message once.

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.

Update Import/Export Functionality to Support Media Data and Third-Party Import

1 participant