Build Journey
KINDAback to the verdict
A local journal whose hard problems are making plain files the real storage while keeping search fast, handling photo timestamps and coordinates honestly, and getting everything out in a form that reimports without loss.
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 Builds the interface.
Get it nodejs.org, or nvm install 22. open ↗
Verify
node --version prints v22 or higher - installfree
Why Builds the desktop shell and the file handling that must not corrupt an entry.
Get it rustup.rs, then the Tauri prerequisites for your platform. open ↗
Verify
cargo tauri info reports no missing dependencies - installfree
Why Search is the feature that makes a long journal usable, and FTS5 is not present in every build.
Get it Most distributions include it; check rather than assume. open ↗
Verify
A create virtual table using fts5 statement succeeds - have readyfree
Why The zone-unknown capture time, the orientation flag and the embedded coordinates are all only testable with real files.
Get it A few pictures from a phone, plus one with location switched off and one rotated.
Verify
Each behaves as its phase requires - decidefree
Why It decides the FTS5 tokenizer, and changing that later means reindexing after the person has already stopped trusting search.
Get it Write down every script you expect to write in, including the occasional one.
Verify
The tokenizer choice in the README names them - have readypennies per month, or free on a spare disk
Why A local-first journal with no offsite copy is one disk failure from nothing.
Get it Any object storage or removable disk with a key you hold.
Verify
A backup written and restored into an empty install
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- On disk: entries/2026/2026-09-11-0743-<id>.md with YAML front matter, and media/2026/09/<id>-<n>.<ext> - entries(id, path, written_at_local, written_at_utc, timezone, latitude, longitude, place_label, body_sha256, indexed_at) - media(id, entry_id, path, mime, bytes, sha256, taken_at_local, taken_at_zone_known, latitude, longitude, width, height) - tags(entry_id, tag) and links(from_entry, to_entry) · both derived from the file, both rebuildable - revisions(entry_id, ordinal, body_sha256, path, at) · previous versions kept as files under revisions/ - entries_fts(entry_id, body) · an FTS5 table whose tokenizer is a recorded decision, not a default nobody looked at - Every table above can be dropped and rebuilt from the folder, and the rebuild is a test rather than a claim.
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_DIR | required | ~/Documents/Journal | The folder that is the journal. Everything else is rebuildable from it. |
INDEX_PATH | required | ~/.local/share/journal/index.db | The SQLite index. Safe to delete, because the rebuild is tested. |
FTS_TOKENIZER | required | unicode61 | Recorded as a decision. Changing it reindexes and touches no entry file. |
REVISION_KEEP | required | 50 | How many previous bodies to keep per entry, as files under revisions/. |
STRIP_LOCATION_ON_EXPORT | required | true | The default for anything leaving the machine. Overridden per action, never globally by accident. |
BACKUP_TARGET | optional | s3://my-bucket/journal | Encrypted backup destination, if you configure one. |
BACKUP_PASSPHRASEsecret | optional | a long passphrase, redacted | Encrypts the backup. Lose it and the backup is noise. |
The build, in order
Files first, index second
Entries live as Markdown on disk and the database can be deleted without losing anything.
The order is the whole guarantee; reversing it makes the folder a copy.
Files
src-tauri/src/storage.rssrc/entry/save.tsA truncated entry file is the one loss this design exists to prevent.
Files
src-tauri/src/storage.rstests/atomic-write.test.tsAn index nobody has rebuilt is an index that cannot be rebuilt.
Files
src/index/rebuild.tstests/rebuild.test.ts
done when · tick each as it passeswatch out- Storing entries in the database and exporting files on demand looks identical for a year and then diverges the first time an export fails halfway or a schema migration goes wrong, and the folder people trusted turns out to have been a stale copy. Write the file first, index afterwards, and test the rebuild so the dependency only runs one way.
Search that works in the language you write in
Full-text search returns the right entries for the scripts and short words the person actually uses.
The default is right for English prose and wrong for anything written without spaces.
Files
src/index/fts.tsdocs/search.mdSilently returning nothing for a two-character search reads as a broken app.
Files
src/search/query.tstests/short-query.test.tsOne script in the fixtures means one script that works.
Files
tests/search-scripts.test.ts
done when · tick each as it passeswatch out- Accepting the default tokenizer and testing with English prose ships a journal where a Japanese or Chinese entry is unfindable, and switching later means reindexing everything after the person has already decided search does not work. Choose the tokenizer against the actual languages in the journal, and put a two-character search and a non-Latin search in the suite on day one.
Photographs, timestamps and places
Imported media keeps its own truth and the entry keeps a time that means something.
Verified by comparing checksums before and after import.
Files
src/media/import.tstests/media-untouched.test.tsThe usual metadata field does not, and guessing makes a holiday land on the wrong days.
Files
src/media/exif.tsThe orientation flag is information, not a defect to be corrected in the file.
Files
src/media/render.tsx
done when · tick each as it passeswatch out- Treating a photograph's capture time as if it were in the machine's current zone puts every picture from a holiday on the wrong day for the rest of the journal's life, and displaying the image without honouring its orientation flag shows half a trip sideways. Record whether the time carried a zone, ask when it did not, and rotate at render time only.
Revisions and a timeline worth trusting
Edits are recoverable and the calendar view never misplaces an entry.
A revision inside the database contradicts the entire storage rule.
Files
src/entry/revisions.tsBackdating is honest only when both are kept.
Files
src/entry/dates.tsConverting to the current zone misfiles every entry written abroad at night.
Files
src/views/timeline.tsxtests/timeline-zones.test.ts
done when · tick each as it passeswatch out- Grouping the timeline by converting each entry's stored instant into the current machine zone moves entries written late at night abroad onto the previous or next day, so a month of travel is silently misfiled. Group by the local date recorded with the entry, and keep the instant only for ordering.
Leaving with everything, and leaking nothingproduct builder
Export, backup and sharing move the whole journal without carrying metadata nobody asked to send.
An export nobody has reimported is a folder of hope.
Files
src/export/run.tstests/export-roundtrip.test.tsThe preview does not show the coordinates, which is exactly why this leaks.
Files
src/share/scrub.tstests/no-gps-on-export.test.tsEncrypted backups fail silently more often than they fail loudly.
Files
src/backup/restore.tstests/restore.test.ts
done when · tick each as it passeswatch out- Sharing a single entry by copying the image file sends the coordinates of the house it was taken in, embedded in metadata nobody sees in the preview, and the person finds out from whoever received it. Strip location from anything leaving by default, make keeping it a deliberate per-action choice, and list the travelling fields in the dialog.
That is the whole plan for Journey. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Multi-device sync.
- Native mobile clients.
- Shared or collaborative journals.
- Hosted storage operated by you.
- high-fidelity color, format, and export handling
- native mobile clients
- conflict-safe multi-device sync
- mature importers and exporters
- collaborative editing and sharing
- A read-only viewer that opens the folder with no database at all, which also proves the storage rule
- Map clustering over entry coordinates, computed locally from the index
- An importer for one other journal's export format, written against real files rather than its documentation
Need the files? The project pack on the verdict page hands your agent the whole brief · more notes & knowledge.