Build Day One
KINDAreplaces $4.17/mosaves $50.04/yrback to the verdict
A private journal where the engineering is unremarkable and the obligations are not: entries encrypted with primitives you did not design, media handled without exhausting a device, and an export that must still open a decade after this application stops being maintained.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree
Why Runs the local application.
Get it nodejs.org, or nvm install 22. open ↗
Verify
node --version prints v22 or higher - installfree
Why Nothing here is designed by you, and a journal is exactly the data where a homemade construction would be least forgivable.
Get it A well-reviewed library providing a memory-hard derivation function and an authenticated encryption mode.
Verify
It produces the published test vectors for both - have readyfree
Why Media handling, browsing performance and export size only become real at the scale a journal reaches after years, which is when it matters most.
Get it Import from an existing journal, or generate several thousand entries with realistic media attached.
Verify
At least three thousand entries with a thousand photographs exist - have readypennies per month
Why The promise is that the contents are unreadable by whoever holds the sync store, which only means anything if the user chose that store.
Get it Any file sync you already use, or object storage with a key you hold.
Verify
An encrypted file written on one device appears on another - decidefree
Why The export is how the writing survives, and choosing a format that requires specific software to read defeats the purpose.
Get it Pick a plain text format with metadata in a readable header, and write down why it will still open in two decades.
Verify
A written note names the format and the reasoning
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- entries(id, body, created_at_utc, created_timezone, place_id, weather_json, tags_json, updated_at) - media(entry_id, id, kind, blob_ref, bytes, captured_at, dimensions) · encrypted, stored outside the entry store - places(id, label, coordinates, timezone) · optional, and omittable per entry - journals(id, name, encryption_params_json, created_at) · several journals with independent keys - revisions(entry_id, ordinal, body, at) · because a journal entry is sometimes revised and the original matters - exports(id, journal_id, format, path, entry_count, verified_at) · verification is part of exporting - The encryption parameters live with each journal, so a journal created years ago still opens after the recommended parameters have moved on.
Environment variables
These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
JOURNAL_PATH | required | ~/.local/share/journal/journal.db | Encrypted entries. The most personal file the machine holds. |
MEDIA_PATH | required | ~/.local/share/journal/media | Encrypted media, stored outside the entry database. |
KDF_PARAMS | required | argon2id:m=47104,t=1,p=1 | Copied from current guidance and written into each journal it creates. |
EXPORT_DIR | required | ~/Documents/journal-export | Plain files. Somewhere your ordinary backups already reach. |
LOCK_IDLE_MINUTES | required | 10 | An unlocked journal on an unattended machine is the exposure. |
SYNC_REMOTE | optional | s3://my-journal/store | Optional and end to end encrypted. Absent means one device, which works completely. |
The build, in order
Writing that is never lost
An entry survives a crash mid-sentence and a decade of use.
Two mechanisms. Per keystroke revisions make the history useless.
Files
src/entries.tsIt will be opened after a long gap, by which time defaults have moved.
Files
src/crypto.tsNot at a clean point. That is the case that matters.
Files
tests/recovery.test.ts
done when · tick each as it passeswatch out- Hard-coding encryption parameters means a journal written today cannot be opened once you raise them, and a journal is exactly the data most likely to be opened after a very long gap. Store the parameters with the journal and upgrade on the next unlock rather than on read.
Photographs without filling the device
Years of entries with media stay manageable on a normal machine.
Otherwise every read and every backup carries gigabytes.
Files
src/media.tsBrowsing a decade should not decode full images.
Files
src/thumbnails.tsA timeline view must not load everything it scrolls past.
Files
src/timeline.ts
done when · tick each as it passeswatch out- Storing photographs inside the entry database makes every read and every backup carry gigabytes, and the journal becomes slow at exactly the point it has become valuable. Keep media in a separate encrypted store referenced by the entry, with thumbnails for browsing.
When and where you wrote it
An entry written while travelling shows the time it was written there.
The time it was written there is the fact worth keeping.
Files
src/time.tsSome entries should not record where you were.
Files
src/places.tsJournals get written on planes and in the countryside.
Files
src/entries.ts
done when · tick each as it passeswatch out- Displaying every entry in the device's current timezone means a journal written on a trip reads as though everything happened at the wrong hour once you return, which for a record of a day is quietly wrong throughout. Store the zone with the entry and display in it.
Sync that keeps the promise
A second device works without the contents becoming readable by anyone else.
The sync store should be opaque even to whoever runs it.
Files
src/sync.tsThe discarded version may be the only record of a day.
Files
src/conflicts.tstests/conflict.test.tsMedia dominates and retransferring it is the slow path.
Files
src/sync.ts
done when · tick each as it passeswatch out- Resolving a sync conflict by taking the most recent version silently discards writing, and in a journal that writing may be the only record of a day. Keep both versions as revisions and let the person decide, since the conflict is rare and the loss is permanent.
An export that outlives the applicationproduct builder
Everything leaves in a format readable without this software.
Readable in a text editor in twenty years, with no software required.
Files
src/export.tsThis is the mechanism by which the writing survives the software.
Files
tests/roundtrip.test.tsA decade of entries should not need a decade of memory.
Files
src/export.ts
done when · tick each as it passeswatch out- An export that loses media, timezones or revisions is a backup you cannot actually restore from, and for a journal that is discovered at the worst possible moment, often years later. Test the round trip in the suite, comparing content, media and metadata rather than checking the export completed.
That is the whole plan for Day One. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Sharing entries with anyone.
- Automatic content from other services.
- Mood or sentiment analysis.
- A hosted service.
- conflict-safe multi-device sync
- mature importers and exporters
- collaborative editing and sharing
- native mobile clients
- On this day, which is the feature that makes a long journal worth having
- Full text search across a decade, done in memory to avoid a plaintext index
- Multiple journals with separate keys, so a shared one and a private one can coexist
Need the files? The project pack on the verdict page hands your agent the whole brief · more notes & knowledge.