Your notes, always available.
An offline-first notes app built with Meteor 3.5. Demonstrates IndexedDB persistence, optimistic UI, cached subscriptions, soft delete, and PWA support using the jam:* package family.
Demo: https://notes-offline.sandbox.galaxycloud.app/
| Runtime | Meteor 3.5 |
| Frontend | React 19 |
| UI | Mantine UI |
| Offline | jam:offline (IndexedDB, auto-sync, cross-tab) |
| Methods | jam:method (optimistic, offline queuing) |
| Pub/Sub | jam:pub-sub (cached subscriptions) |
| Soft Delete | jam:soft-delete |
| Validation | Zod |
| PWA | Workbox (via workbox-webpack-plugin + Rspack) |
| i18n | Lingui (SWC macro + minimal JSON catalogs) |
| Tests | Mocha |
| Code Quality | ESLint (flat config: react, hooks, jsx-a11y, import-x) + Prettier |
| E2E | Playwright |
| Build | Rspack |
- Create, edit, delete notes with auto-save (500ms debounce)
- Trash with recovery and permanent delete
- Search notes by title, content, or tags
- Pin important notes to the top
- Tags support
- Markdown editing with live preview
- Export/import notes as JSON
- Auto dark mode (follows system preference via Mantine
colorScheme="auto") - Keyboard shortcuts (
Alt+Nnew note,Escdeselect) - Online/offline/syncing status indicator
- Installable as PWA, works fully offline
- Multi-language UI (English, Spanish, Portuguese) with per-device preference
meteor npm install
npm startVisit http://localhost:3000/.
| Command | What it does |
|---|---|
npm start |
Start the app |
npm test |
Integration tests (Mocha, watch mode) |
npm run test:headless |
Integration tests (Mocha, headless/CI) |
npm run lint |
Lint and check formatting |
npm run lint:fix |
Lint and auto-fix + format |
npm run e2e |
E2E tests (Playwright, interactive UI) |
npm run e2e:headless |
E2E tests (Playwright, headless) |
npm run i18n:extract |
Extract messages from source to JSON |
Before running E2E tests for the first time, install Playwright's browsers with npx playwright install.
imports/
api/notes/
collection.js # Mongo.Collection + jam:soft-delete
schema.js # Zod schemas
methods.js # jam:method definitions
publications.js # Publications
locales/
en/messages.json # Source catalog (generated by `lingui extract`)
es/messages.json # Spanish translations
pt/messages.json # Portuguese translations
ui/
App.jsx # MantineProvider + AppShell layout
NotesList.jsx # Sidebar with search + note cards
NoteEditor.jsx # Editor with auto-save
EmptyState.jsx # Empty state
owner.js # Per-device ownerId helper (localStorage)
i18n.js # Lingui bootstrap + activateLocale
client/
main.jsx # Entry point
main.css # Mantine styles
server/
main.js # Import API modules
jam:offlinestores data in IndexedDB and syncs with the server when connectedjam:methodqueues method calls while offline and replays them on reconnectjam:pub-subcaches subscription data so the app loads instantly from cachejam:soft-deletemarks items as deleted instead of removing them, enabling trash and recovery- Workbox service worker (configured through Rspack) caches pages and assets for full offline PWA support
Lingui is integrated into the build pipeline in two complementary ways:
@lingui/swc-pluginis registered in.swcrcso the macros (t,<Trans>,<Plural>) are transformed by Meteor's built-in SWC loader at compile time. No rspack config changes, no Babel.@lingui/loadercompiles the JSON catalogs on the fly during bundling (see the inline-loader import inimports/ui/i18n.js), so there's no separate compile step and no generated.mjsfiles to commit or ignore.
Catalogs live as minimal flat JSON under imports/locales/{en,es,pt}/messages.json. The initial locale comes from localStorage if set, otherwise from navigator.language, falling back to English. A language dropdown in the sidebar header lets users switch locale, and the choice is persisted to localStorage['notes-offline.locale'].
Macros in use:
// Static JSX strings
<Trans>Your notes, always available</Trans>
// Dynamic labels, placeholders, aria attributes, with interpolation
<TextInput placeholder={t`Search notes...`} />
<ActionIcon aria-label={t`Remove tag ${tag}`} />
// Plural-aware counters, per-locale rules (ICU MessageFormat)
<Plural value={notes.length} one="# note" other="# notes" />At build time @lingui/swc-plugin rewrites each macro call into a plain i18n._(id) lookup against the compiled catalog. The hashed IDs (FEKiRx, bpgy4J, etc.) you see in imports/locales/{locale}/messages.json are generated from the macro sites.
Workflow to add or change a string:
- Edit the source (use
<Trans>Hello</Trans>in JSX ort`Hello`in expressions). - Run
npm run i18n:extractto refresh the JSON catalogs. - Fill in translations in
imports/locales/es/messages.jsonandimports/locales/pt/messages.json. - No compile step.
@lingui/loaderpicks up the updated JSON on the next bundle.
To add a new locale, append it to locales in lingui.config.js, add it to SUPPORTED_LOCALES in imports/ui/i18n.js, then run npm run i18n:extract.
- Galaxy:
meteor deploy your-app.meteorapp.com- To try it quickly with a free tier and shared MongoDB:
meteor deploy your-app.meteorapp.com --free --mongo
- To try it quickly with a free tier and shared MongoDB:
- Any Node.js host:
meteor buildgives you a standard Node bundle - MUP: automated deploy to your own server over SSH