Back to Portfolio
ProductLocal-FirstEncryption

Notes, Reminders & To-Dos Inside a Calendar

Why I built the note-taking layer directly into the Nepali calendar instead of shipping a separate app — local-first storage, BS-native reminders, and recurring tasks that handle themselves.

RJ
Romanch Jung Rayamajhi
April 2026
8 min read
BS
Native date system
E2E
Encrypted at rest
0ms
Network wait to save
Free
No ads, ever
Table of Contents
  1. The Problem With Separate Apps
  2. BS-Native Everything
  3. Local-First Architecture
  4. How Reminders Actually Work
  5. Recurring Tasks
  6. Privacy by Design
  7. What I'd Do Differently

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.

💡
The insight: a calendar is already the natural home for notes and tasks. Every note has a date. Every to-do has a deadline. Splitting them across apps is an artifact of how software got sold, not how people think.

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.

data model
// 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.

save flow
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
  });
}
The user experience difference is dramatic. Typing a note on a bad connection feels identical to typing on fibre — because nothing blocks on the network.

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.

reminder scheduling
// 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);
});
⚠️
Hard-won lesson: browsers throttle and batch alarms. An alarm set for 9:00 AM might fire at 9:04. For medicine reminders and exam deadlines, I schedule a few minutes early and show the intended time in the notification body.

05Recurring Tasks

Repeats are expressed in BS terms, which makes patterns Nepali users actually need possible:

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:

🔒
The simplest privacy guarantee is not having the data. Where a feature can work locally, it works locally.

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.

🚀
Notes, to-dos, and reminders are free on Calenote — on the web and in the browser extension, working offline, in your own calendar system.