01The Problem With Separate Apps
Most people in Nepal juggle three things: a calendar app in English dates, a notes app that knows nothing about Dashain, and a reminder app that can't express “every Purnima.”
The friction isn't any single app — it's the translation tax between them. You think in Bikram Sambat, your tools think in Gregorian, and you spend cognitive effort converting all day long.
02BS-Native Everything
The critical design decision: Bikram Sambat is the primary date system, not a display translation layer. When you set a reminder for “15 Ashoj,” it stores 15 Ashoj — not a Gregorian timestamp that gets converted back for display.
// A note is anchored to a BS date, not a JS Date { id: "note_a7f3", bsDate: { year: 2083, month: 6, day: 15 }, // 15 Ashoj 2083 adDate: "2026-10-01", // derived, for sorting/sync title: "Exam form deadline", body: "...", tags: ["college"], updatedAt: 1759286400000 }
This matters more than it sounds. A birthday on 5 Mangsir should recur on 5 Mangsir every year — which lands on a different Gregorian date each time. Store it as Gregorian and the recurrence drifts. Store it as BS and it's correct forever.
📅 Dual display
BS and AD shown side by side everywhere. No mental conversion needed at any point.
🌙 Tithi aware
Set reminders for lunar days — Purnima, Aunsi, Ekadashi — not just fixed dates.
🎉 Holiday context
Public holidays and festivals appear inline, so scheduling around Dashain is obvious.
🔄 Correct recurrence
Yearly events recur on the BS date, matching how people actually remember them.
03Local-First Architecture
Every write goes to local storage first, then syncs in the background. The UI never waits on the network.
async function saveNote(note) { // 1. Write locally — instant, always succeeds await localDB.notes.put({ ...note, syncState: "pending" }); // 2. Update UI immediately renderNote(note); // 3. Sync in the background, retry on failure queueSync(note.id).catch(() => { // Stays "pending" — retried on next connection }); }
Conflict resolution
When the same note is edited on two devices, last-write-wins by updatedAt timestamp, but the losing version is kept as a revision rather than discarded. Silently destroying someone's writing is unforgivable.
04How Reminders Actually Work
Reminders are harder than they look because they need to fire reliably across three surfaces: the web app, the browser extension, and push notifications — while the user may have all, some, or none of them open.
// Never setTimeout — it dies with the page/worker // Always the Alarms API for the extension chrome.alarms.create(`reminder_${id}`, { when: firesAtMs }); chrome.alarms.onAlarm.addListener(async (alarm) => { if (!alarm.name.startsWith("reminder_")) return; const reminder = await getStored(alarm.name); chrome.notifications.create({ type: "basic", iconUrl: "icon-128.png", title: reminder.title, message: reminder.bsDateLabel // "१५ असोज २०८३" }); // Reschedule if recurring if (reminder.repeat) scheduleNext(reminder); });
05Recurring Tasks
Repeats are expressed in BS terms, which makes patterns Nepali users actually need possible:
- Every day — medicine, habits
- Every week — classes, gym
- Every BS month on day N — rent, EMI, salary
- Every BS year on date — birthdays, anniversaries, shraddha
- Every Purnima / Aunsi — lunar observances
That last one is the differentiator. No mainstream calendar app can express “remind me every Purnima” — but for a lot of families in Nepal, that's a real recurring obligation.
06Privacy by Design
Notes contain the most private things people write — health worries, money problems, thoughts they haven't said aloud. So the architecture makes it structurally hard to misuse:
- Encrypted at rest — sensitive vaults use a passphrase that never leaves the device
- Zero-knowledge for the vault — I literally cannot read what's in a user's password or health vault
- No ad SDKs — there is no third-party analytics or advertising code in the app at all
- No data sales — the business model is optional premium, not surveillance
07What I'd Do Differently
Start with the sync model.
I built local storage first and bolted sync on later. Designing the conflict model up front would have saved a painful refactor.
Version the schema from day one.
Migrating stored notes when the shape changes is much easier when every record carries a schemaVersion.
Test on real bad networks.
Chrome DevTools throttling is not the same as actual patchy mobile data. Real-world testing caught bugs the simulator never surfaced.