Baby Movement Tracker pada asalnya cuma web app kecil untuk kira kick bayi. Tapi lepas versi pertama dah boleh pakai, baru nampak kerja engineering yang sebenar. Cabaran utama bukan tambah banyak screen. Cabaran sebenar ialah nak pastikan proses tracking harian rasa reliable bila app digunakan betul-betul: internet tak stabil, dashboard state tak sync, bug pada boundary tarikh, dan flow onboarding tanpa login tapi data user masih terasing.

Post ini ringkaskan perubahan yang bawa app ini daripada counter yang simple kepada offline-first PWA yang lebih production-ready. Versi semasa boleh cuba dekat bb.hafism.xyz.

Problem Statement

Untuk produk macam ini, interaction utama memang kena kekal laju:

  • buka app
  • tap sekali untuk record movement
  • tengok counter update terus
  • yakin data masih betul walaupun network tak stable

Nampak simple, tapi bila dah guna betul-betul memang cepat nampak beberapa masalah:

  • UI terlalu bergantung pada server round-trip
  • dashboard dengan history kadang-kadang boleh lari sync
  • grouping ikut tarikh ada timezone edge case
  • app perlukan cara yang low-friction untuk asingkan data user tanpa full auth flow
  • support iOS perlukan strategi packaging yang practical tanpa maintain second app stack

Architecture Overview

Mobile Browser / PWA / iOS WKWebView
                |
                v
        App shell + local queue
                |
                v
      Sync layer + optimistic state
                |
                v
     Cloudflare-hosted web application
                |
                v
         Persistent movement records

Matlamat dia mudah: write path kena responsive di client dulu, kemudian baru reconcile dengan backend bila connectivity mengizinkan.

What Changed

Faster Daily Recording Flow

Flow utama untuk kira kick sekarang rasa lebih macam tool sebenar, bukan sekadar form yang tunggu request siap.

Antara improvement utama:

  • one-tap recording dari main screen
  • optimistic UI supaya count terus berubah sebelum network request habis
  • toast feedback lepas record berjaya
  • support untuk undo entry paling latest
  • history yang boleh expand untuk nampak session dengan lebih jelas
  • dashboard summary dan month navigation yang lebih kemas

Dalam implementasi sebenar, perubahan paling penting ialah optimistic path ini:

async function recordKick() {
  const pendingEntry = createLocalEntry();
  updateCounter(pendingEntry);
  queueForSync(pendingEntry);

  try {
    await syncPendingEntries();
    showToast("Kick recorded");
  } catch {
    markEntryAsPending(pendingEntry.id);
    showToast("Saved offline. Will sync automatically.");
  }
}

Ini yang buat app masih rasa responsive walaupun request path tak available buat sementara waktu.

Daily count screen

Offline-First Reliability

Improvement paling besar dari sudut reliability ialah saya mula treat offline behavior sebagai keperluan utama, bukan sekadar progressive enhancement.

Pendekatan semasa merangkumi:

  • service worker app-shell caching
  • local queue untuk write yang tak boleh dihantar terus
  • background sync bila connection kembali pulih
  • sync status messaging yang lebih jelas
  • handling yang lebih explicit untuk pending dan unsynced record

Dashboard and State Consistency

Build sebelum ini ada masalah yang agak common untuk single-page app: satu bahagian UI dah update, tapi bahagian lain masih tunjuk stale data.

Saya betulkan update path supaya event write yang sama terus refresh:

  • current session counter
  • calendar dashboard
  • aggregated daily summaries
  • history views

Jadi tak perlu lagi tutup dan buka semula app semata-mata nak tengok total yang betul.

Dashboard with calendar summaries

Date and Timezone Fixes

Dalam tracking app macam ini, date handling sebenarnya lagi penting daripada yang orang selalu sangka. Bug timezone yang kecil pun boleh senyap-senyap rosakkan daily summary.

Fix kali ini fokus pada:

  • ketepatan calendar highlight
  • day grouping yang konsisten antara view
  • date parsing yang lebih selamat antara client dan persistence layer

Lightweight Multi-User Separation

Saya masih kekalkan model tanpa login, tapi identity layer sekarang lebih jelas.

App sekarang guna device-based anonymous user ID:

  • generate automatik pada first use
  • disimpan secara local pada device
  • digunakan untuk asingkan record tanpa friction dari segi registration
  • dipaparkan pada footer untuk mudahkan debugging dan support

Ini memang bukan pengganti untuk full authentication, tapi untuk single-purpose consumer tool macam ini, ia lebih practical sebab onboarding speed lebih penting daripada cross-device account sync.

Baby Profile and Age Metadata

Saya tambah profile card yang ringan pada dashboard, bukan pada main counter screen.

User sekarang boleh simpan:

  • nama bayi
  • tarikh lahir
  • umur yang dikira dalam hari, minggu, bulan, dan tahun

Peletakan itu memang sengaja. Counter view kekal fokus pada kerja utama, manakala profile data cuma jadi passive context yang sentiasa available bila perlu.

Baby profile card

iOS Packaging Strategy

Saya juga tengah siapkan versi iOS, tapi implementasi semasa memang sengaja dipilih secara conservative.

Daripada rewrite app ini secara native, build untuk iPhone sekarang guna thin SwiftUI container di sekeliling web app sedia ada melalui WKWebView.

Trade-off dia buat masa ini memang masuk akal:

  • satu product logic path
  • improvement pada frontend boleh dikongsi antara web dan iOS
  • kos maintenance lebih rendah berbanding maintain feature set berasingan
  • laluan lebih cepat untuk public release

Step-by-Step: Shipping a Reliable Update

Secara ringkas, release process untuk perubahan dalam app ini sekarang lebih kurang macam ini:

  1. Update flow utama web app dan verify optimistic state transition.
  2. Test offline behavior dengan paksa write gagal dan tengok recovery sync.
  3. Validate total pada dashboard dan history grouping lepas replay queued entries.
  4. Check behavior pada boundary tarikh untuk view yang sensitif dengan timezone.
  5. Confirm build yang sama masih behave dengan betul dalam iOS wrapper.
  6. Deploy dan monitor regression pada data consistency, bukan UI sahaja.

⚠️ Gotchas / Lessons

  • Offline support bukan setakat caching. Bahagian yang susah sebenarnya ialah write reconciliation dan macam mana nak pastikan pending state masih nampak jelas.
  • Optimistic UI cuma selamat kalau rollback dan retry path betul-betul explicit.
  • Date logic patut dilayan sebagai sebahagian daripada data integrity, bukan sekadar formatting detail.
  • Identity tanpa login memang lajukan onboarding, tapi ada limit yang jelas untuk cross-device continuity dan recovery.
  • WKWebView wrapper ialah delivery strategy, bukan pengganti penuh untuk native capability. Ia sesuai bila product surface masih banyak berubah.

Outcome

App ini masih sengaja dikekalkan kecil dan ringkas, tapi sekarang jauh lebih dependable:

  • interaction loop utama lebih laju
  • behavior lebih baik bila connectivity tak cantik
  • update pada dashboard dan history lebih tepat
  • kurang inconsistency berkaitan tarikh
  • ada laluan yang lebih practical untuk distribution ke iOS tanpa duplicate seluruh stack

TL;DR

Baby Movement Tracker sekarang bukan lagi sekadar kick counter yang simple. Improvement utama dalam update ini ialah offline-first write handling, optimistic UI, fix untuk state consistency, date logic yang lebih selamat, anonymous device identity, dan pendekatan iOS yang practical menggunakan WKWebView.

Lesson paling besar untuk saya: produk tracking yang nampak simple boleh jadi technical dengan sangat cepat bila reliability mula jadi keutamaan. Kebanyakan kerja bukan pada tambah feature baru, tapi pada memastikan satu tap itu rasa immediate, betul, dan boleh dipercayai merentas network failure, stale UI state, dan platform yang berbeza.