- Rust 44.6%
- Svelte 31.6%
- JavaScript 20.7%
- Just 1.3%
- Dockerfile 1.1%
- Other 0.7%
|
|
||
|---|---|---|
| .forgejo/workflows | ||
| docs | ||
| pwa | ||
| server | ||
| sync | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| Dockerfile | ||
| Justfile | ||
| LICENSE | ||
| PLAN.md | ||
| README.md | ||
| renovate.json | ||
| rust-toolchain.toml | ||
navinote
A personal note-taking PWA with reminders and markdown sync.
Components
- pwa/ - Svelte 5 progressive web app
- server/ - Axum REST API with SQLite
- sync/ - CLI tool to sync notes to markdown files
Quick Start
# Install dependencies
cd pwa && npm install
# Build everything
just build
# Run locally
just dev-server # API on :8080
just dev-pwa # Vite dev server
CLI Sync
Install the sync tool:
just install
First-time login (browser-based, once per workstation):
export NAVINOTE_OIDC_ISSUER="https://auth.navicore.tech/realms/homelab"
export NAVINOTE_OIDC_CLIENT_ID="navinote-sync"
navinote-sync login
Then run sync (cron-friendly — no env needed beyond NAVINOTE_ZET_DIR):
export NAVINOTE_ZET_DIR="$HOME/notes"
# NAVINOTE_URL defaults to https://notes.navicore.tech
navinote-sync
Synced notes are appended to daily markdown files (YYYY-MM-DD.md) with reminders formatted as:
* [ ] #reminder 2026-01-30T14:00:00Z: Call dentist
* [x] #reminder 2026-01-30T09:00:00Z: Morning standup
Features
- Offline-first with IndexedDB
- Swipe left to delete, swipe right to mark done
- Reminder color coding (green=future, orange=overdue, dimmed=done)
- iOS/Android install prompts
CI
CI is defined in .forgejo/workflows/ci.yml and runs on every PR and push to main. It invokes a single command — just ci — which chains:
fmt-check—cargo fmt --all -- --checklint—cargo clippy --locked --workspace --all-targets -- -D warningsplus a strict PWA build (SVELTE_STRICT=1)test—cargo test --locked --workspace --all-targetsbuild— PWA, server, and sync release builds
The Rust toolchain is pinned to 1.93.0 in two places that must stay in sync: rust-toolchain.toml (local dev) and the toolchain: input of the CI workflow. All cargo invocations use --locked, so a stale Cargo.lock fails the build instead of silently re-resolving.
Run the exact same checks locally before pushing:
just ci
Environment Variables
Server:
| Variable | Required | Default | Description |
|---|---|---|---|
| NAVINOTE_OIDC_ISSUER | Yes | - | Realm issuer URL (e.g. https://auth.example.com/realms/navinote) |
| NAVINOTE_OIDC_AUDIENCES | Yes | - | Comma-separated aud values to accept. With anz this is the realm URL (e.g. https://auth.example.com/realms/homelab) — anz puts the resource-server identifier in the access token's aud, not the client_id. |
| NAVINOTE_BOOTSTRAP_SUB | One-shot | - | Operator sub used to backfill user_id on pre-OIDC notes. Set for one deploy, then unset. |
| NAVINOTE_DB_PATH | No | navinote.db | SQLite database path |
| NAVINOTE_PORT | No | 8080 | Server port |
| NAVINOTE_STATIC_DIR | No | dist | Static files directory |
Sync CLI:
First-time setup uses OIDC. Run navinote-sync login once on a workstation with a browser — it opens the auth page, catches the callback on http://127.0.0.1:8765/callback, and writes the refresh token to $XDG_STATE_HOME/navinote-sync/credentials.json (mode 0600). Subsequent cron-driven runs use that refresh token (rotated every run).
| Variable | When | Default | Description |
|---|---|---|---|
| NAVINOTE_OIDC_ISSUER | login only |
- | Realm issuer URL (e.g. https://auth.example.com/realms/navinote) |
| NAVINOTE_OIDC_CLIENT_ID | login only |
- | The PWA/CLI public client (e.g. navinote-sync) |
| NAVINOTE_URL | sync | https://notes.navicore.tech | API URL |
| NAVINOTE_ZET_DIR | sync | - | Markdown output directory (required) |
If the refresh token expires or is revoked, the next sync writes a #reminder line to today's zet file (navinote-sync needs re-auth — run \navinote-sync login``) and exits non-zero.