Skip to content

Releases: prabhavalabs/MendLog

v1.0.7 — Stop auto-sync ping-pong

Choose a tag to compare

@theetaz theetaz released this 25 Apr 12:33

Critical hotfix on top of v1.0.5/6.

🛑 Auto-sync no longer runs every 3 seconds

v1.0.5 wired the local data-change signal to kick a sync 3s after every notification. The same signal is also used as a "data merged, refresh the UI" message — fired at the end of every sync and after every realtime merge. Net result: each sync scheduled the next one, forever. Users saw the sync icon spinning on what looked like a 1-second cadence as overlapping passes piled up and burnt battery.

The fix tags every `notifyLocalDataChanged` call with its source — `'user'`, `'sync'`, or `'realtime'`. The auto-sync trigger only listens for `'user'`. Sync's own tail and realtime merges still refresh the UI but no longer rearm the sync that just produced them.

No other behavioural changes.

  • Capture is still instant (v1.0.5)
  • Auto-sync still fires within 3 seconds of saving a job (v1.0.5)
  • Stuck-upload recovery + Retry button still work (v1.0.6)

Distribution

`./deploy.sh --notes "Stop auto-sync ping-pong — see GitHub release v1.0.7"`

v1.0.6 — Upload retry hardening

Choose a tag to compare

@theetaz theetaz released this 25 Apr 11:08

Patch release that hardens the binary upload pipeline against flaky-network and process-interrupt edge cases.

🛡 Stuck uploads recover automatically

A row whose upload was interrupted (process killed, JS crash, OS swept the app) used to sit orphaned in `upload_state='uploading'` forever. The drainer now treats that state the same as 'pending' — same row, fresh attempt — so the next sync pass cleans it up without any user action.

⏳ Per-row backoff (30s) so flaky networks don't burn the budget

Previously, five rapid syncs while offline could exhaust the `MAX_ATTEMPTS=5` budget in seconds, leaving the row quarantined even after connectivity returned. New `last_attempt_at` column gates each retry by a 30-second cooldown, and `MAX_ATTEMPTS` bumps to 10 — at least five minutes of intermittent connectivity before a row goes cold.

📡 Network reconnect resets the attempt counter

NetInfo offline→online transitions now call a soft reset on every not-yet-uploaded row before kicking the sync (3s debounce). A user who lost the network mid-batch and burnt 8 attempts during the outage gets a fresh budget the moment Wi-Fi reconnects.

🔁 "Retry stuck uploads" button on the Me screen

When the queue has anything pending, a new button appears beneath "Retry AI processing" that calls `resetFailedUploads` and forces an immediate sync. Self-service recovery without reinstalling.

Schema migration

`drizzle 0003` adds `last_attempt_at integer` to `job_photos` and `job_clips`. Auto-applies on first launch via the existing `useOfflineMigrations` hook — no user action required.

Distribution

Build and ship the Android APK to Firebase App Distribution: `./deploy.sh --notes "Upload retry hardening — see GitHub release v1.0.6"`

v1.0.5 — Capture and sync hotfix

Choose a tag to compare

@theetaz theetaz released this 25 Apr 10:50

Hotfix release on top of v1.0.4. Two regressions surfaced during testing:

📷 Photo capture is instant again

Capture-time photo optimization in v1.0.4 stalled the picker for 30s+ on
Android — manipulator + blurhash both round-trip the native bridge with
full-resolution bitmaps, and Promise.all serialised them on a single
native thread. The thumbnail wouldn't appear until both finished.

The optimization (resize to 1600 px, JPEG q 0.7, blurhash compute) now
runs inside the upload pipeline instead of the picker. Capture stages
the original URI immediately so the thumbnail shows up the instant the
picker returns; the optimization happens silently in the background as
part of the upload.

🔁 Auto-sync now actually fires after saving

Saving a job left the queue stuck at "1 pending" until the user
foregrounded the app or pulled to sync. The local data-change bus only
refreshed the count badge — nothing called the sync engine. Adding a
3-second debounced auto-sync on every local mutation fixes this; the
queue clears while the user is still on the screen.

🛡 Trailing-edge re-run for in-flight syncs

Strict single-flight (added in v1.0.4) silently coalesced writes that
arrived during a running sync into the in-flight pass — but that pass
already snapshotted its dirty rows before the new write existed.
Replaced with a trailing-edge pattern: callers arriving during a run
mark a pending re-run, and one more pass fires after the current one
completes.

Distribution

Build and ship the Android APK to Firebase App Distribution: `./deploy.sh --notes "Capture and sync hotfix — see GitHub release v1.0.5"`

v1.0.4 — Media pipeline reliability

Choose a tag to compare

@theetaz theetaz released this 25 Apr 09:51

Highlights

  • 📷 Smaller, faster photo uploads. Photos are now resized to 1600 px on the long edge and recompressed at JPEG q 0.7 the moment you take them. Typical capture drops from ~1.3 MB to ~150–300 KB — 5–8× faster on cellular, no loss of AI usable detail.
  • 🌫 Blurhash placeholders. Photos now show an instant tinted placeholder while the image loads, so the grid no longer flashes empty tiles.
  • 🔁 Photos and clips appear after a fresh install. Reinstalling the app, switching devices, or clearing app data used to make photos and clips invisible even though the server still had them. Pull now hydrates anything missing locally. The "Sync all history" button covers media too, not just jobs.

Reliability fixes

  • Eliminated a sync race that could create duplicate `job_photos` rows when the engine triggered twice in the same second.
  • Job, photo, and clip pushes are now idempotent end-to-end via `client_id` + UNIQUE indexes on the server. A crash between the server INSERT and the local "synced" mark can no longer cause a duplicate on the next launch.
  • `runDataSync` is single-flight: concurrent triggers (foreground tick + connectivity recovery + realtime kick) share one in-flight pass.
  • One-shot hydration step on the first sync after this build resets the photo/clip cursors so the new pull-INSERT path can fill in any rows the device was missing.

Server-side companions (already live)

  • Storage RLS extended for admin reads on `job-photos` and `job-audio`.
  • `client_id` UNIQUE indexes on `jobs`, `job_photos`, `job_clips`.
  • `(job_id, storage_path)` / `(job_id, audio_path)` UNIQUE belt-and-braces.
  • Nightly orphan-storage cleanup cron at 03:10 UTC.
  • Admin-gated `backfill-photo-resize` edge function for one-shot reprocess of legacy oversize photos.

Audio recorder

  • Already at the floor for usable speech (16 kHz mono AAC at 32 kbps). No change needed.

Distribution

  • Build and ship the Android APK to Firebase App Distribution: `./deploy.sh --notes "Media pipeline reliability — see GitHub release v1.0.4"`