Live app: https://alicommit-malp.github.io/wherewasi/ · Source: https://github.com/alicommit-malp/wherewasi
Most task apps are built around what you have to do. wherewasi is built around a different moment: the one where you stop doing one thing and start doing another, and your entire mental context evaporates. It answers exactly one question, and it answers it fast — where was I?
The problem it solves
You have five things in flight. You're deep in one of them when something interrupts you. Two days later you come back and spend fifteen minutes reconstructing what you'd already figured out: what you'd finished, what you were blocked on, what the next move was going to be.
That reconstruction is pure waste, and it's avoidable — the knowledge existed in your head at the moment you left. wherewasi is a place to dump it in five seconds, and to find it again instantly.
Three rules define the whole app:
- Exactly one task is active at a time. Not a philosophy about focus — just an honest record of where your attention actually is.
- Every task carries a stack of short notes. Newest first. "renewed the cert", "waiting on DNS", "next: draft the outline". Free text, no schema, no ceremony.
- Switching is instant. One click or one keystroke. No dialog, no prompt, no "are you sure" — because anything that adds friction to switching means you'll stop recording your switches, and then the app is worthless.
What it deliberately does not do
Design is mostly subtraction, and this app had a lot subtracted:
- No timers, no time tracking. The first draft measured how long each task was active. It got cut, because the value was in restoring context, not producing timesheets — and a running clock creates a low-grade obligation to "start" and "stop" things correctly.
- No tags or categories. An earlier version tagged each note as did / waiting / next. In practice the tag was one more decision at the exact moment you want zero decisions. Plain text carries the same meaning: just type "waiting on DNS".
- No forced note on switch. The original design popped a modal on every switch asking what happened. It was correct in theory and unbearable in practice.
- No accounts, no server, no network. Your data never leaves your browser.
Each of those removals came from actually using the thing and noticing the friction.
Using it
The board
One narrow column. Each row is a task: a dot, the title, and its three most recent notes underneath in muted text. The active task is highlighted and is the only row with a note input. That's the whole interface.
Clicking a task title switches to it instantly and moves it to the top of the list, so the board self-sorts by recency: what you're on is first, what you just left is right below it, and stale work sinks to the bottom on its own.
Writing notes
The active task's input says What happened?. Type, press Enter, done. Notes stack newest-first, each with a small timestamp so you can see whether a thought is from ten minutes ago or last week.
Click any note to edit it inline (Enter saves, Escape cancels). Hover a note and a × appears to delete it. If a task has more than three notes, a +N more link expands the full history — and that expansion collapses automatically the moment you switch tasks, so the board never grows cluttered behind your back.
Keyboard
The app is fully keyboard-driven; press ? at any time for this list.
| Key | Action |
|---|---|
↓/j, ↑/k
|
Move the selection |
Enter |
Switch to selected — on the active task, jump into the note input |
a |
Write a note on the active task, from anywhere |
n |
New task |
e |
Rename selected task |
d |
Done — archive selected task |
Space / m
|
Expand / collapse selected task's notes |
v |
Toggle the archive |
/ |
Search (in the archive) |
Esc |
Leave input / close view / clear selection |
The fastest possible loop is j j Enter to land on a task, then Enter again to start typing what happened. Single-letter shortcuts are suppressed while you're typing, so notes never trigger commands.
Finishing and finding things
Pressing d (or clicking the ✓ that appears on hover) archives a task. It leaves the board but keeps its entire note stack, browsable under Archive and reopenable at any time. The archive has full-text search across both task titles and note contents — so months later, searching "headcount" surfaces the task where you wrote "travel numbers imported, headcount still missing", even if the title never mentioned it.
How it's built
A deliberately small stack, chosen so the whole app stays readable in one sitting:
- Vite 8 + Svelte 5 (runes) + TypeScript — small bundle, minimal boilerplate
- Dexie 4 over IndexedDB — typed local persistence
- vite-plugin-pwa — generated service worker and manifest; installable and fully offline
-
GitHub Actions → GitHub Pages — every push to
mainbuilds and publishes
The data model
Three tables, all with UUIDs and timestamps:
Task { id, title, status: 'open' | 'archived', createdAt, archivedAt?, sortOrder }
Note { id, taskId, text, createdAt }
AppState { id: 'app', activeTaskId: string | null } // exactly one row
The most important detail is the smallest one: "one active task" is a single pointer in AppState, not a boolean flag on each task. Two tasks being active simultaneously isn't a bug that has to be prevented — it's unrepresentable. Whole classes of state-sync bugs simply don't exist.
sortOrder is what makes switching bubble a task to the top: activating a task rewrites its order to just below the current minimum, and that reordering is persisted like everything else.
State flow
A single store class (src/lib/store.svelte.ts) holds $state runes, hydrated from IndexedDB once at startup. Every mutation writes to Dexie first, then updates in-memory state. There is no memory-only state anywhere, so a refresh, a crash, or a closed laptop lid loses nothing. Empty titles and empty notes throw rather than silently no-op.
The UI is four small components — the board, a task row, the note form, and a help overlay — none of which own any persistent state of their own.
Local-first, on purpose
Everything lives in your browser's IndexedDB. No account, no sync, no telemetry, no network calls at all. That has real consequences worth being honest about:
- It's single-device. The data model was built sync-ready (UUIDs and timestamps everywhere), so a sync layer can be added later without remodeling — but today, your board lives in one browser profile.
- Browser storage isn't forever. Clearing site data wipes it, and browsers can evict storage under disk pressure. Requesting persistent storage and adding a JSON export are the obvious next hardening steps.
For a tool whose whole job is holding your working context, that trade is deliberate: zero setup, zero latency, and nothing to trust.
Where it goes next
The honest backlog, roughly in priority order:
-
Durability —
navigator.storage.persist()plus JSON export/import, so the data survives. - Sync — a self-hosted backend (PocketBase or CouchDB/PouchDB) that the static frontend talks to, giving multi-device use without handing task contents to a third party.
-
Multi-tab consistency — the store loads once at startup, so two open tabs currently drift apart. Dexie's
liveQueryor aBroadcastChannelfixes it. - Undo — note deletion is instant and permanent; a brief undo affordance would fit the app's no-confirmation style better than a dialog.
-
Tests — the store's invariants (one active task, archiving clears the pointer, switching reorders) are exactly the logic worth pinning down with Vitest and
fake-indexeddb.
Try it
https://alicommit-malp.github.io/wherewasi/ — it installs as a PWA from the browser menu and works with the network off. Add a couple of the things you're juggling right now, switch between them, and leave yourself a note on the way out. The payoff arrives the next time you come back.