Build Day One

KINDAreplaces $4.17/mosaves $50.04/yrback to the verdict

0%0 of 17 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

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.

estimated effort multi-daythe files for this build are in the project pack

EncryptionA maintained library with parameters stored alongside the dataStorageLocal database with encrypted media stored separatelySyncOptional, end to end, through storage the user controlsTimeEntry time recorded with its timezone and location where givenExportPlain formats containing everything, round trip tested

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

  1. installfree

    Why Runs the local application.

    Get it nodejs.org, or nvm install 22. open ↗

    Verify node --version prints v22 or higher

  2. 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

  3. 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

  4. 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

  5. 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.

VariableNeededExampleWhere the value comes from
JOURNAL_PATHrequired~/.local/share/journal/journal.dbEncrypted entries. The most personal file the machine holds.
MEDIA_PATHrequired~/.local/share/journal/mediaEncrypted media, stored outside the entry database.
KDF_PARAMSrequiredargon2id:m=47104,t=1,p=1Copied from current guidance and written into each journal it creates.
EXPORT_DIRrequired~/Documents/journal-exportPlain files. Somewhere your ordinary backups already reach.
LOCK_IDLE_MINUTESrequired10An unlocked journal on an unattended machine is the exposure.
SYNC_REMOTEoptionals3://my-journal/storeOptional and end to end encrypted. Absent means one device, which works completely.

The build, in order

  1. Writing that is never lost

    An entry survives a crash mid-sentence and a decade of use.

    1. Two mechanisms. Per keystroke revisions make the history useless.

      Files src/entries.ts

    2. It will be opened after a long gap, by which time defaults have moved.

      Files src/crypto.ts

    3. Not at a clean point. That is the case that matters.

      Files tests/recovery.test.ts

    done when · tick each as it passes
    watch 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.
  2. Photographs without filling the device

    Years of entries with media stay manageable on a normal machine.

    1. Otherwise every read and every backup carries gigabytes.

      Files src/media.ts

    2. Browsing a decade should not decode full images.

      Files src/thumbnails.ts

    3. A timeline view must not load everything it scrolls past.

      Files src/timeline.ts

    done when · tick each as it passes
    watch 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.
  3. When and where you wrote it

    An entry written while travelling shows the time it was written there.

    1. The time it was written there is the fact worth keeping.

      Files src/time.ts

    2. Some entries should not record where you were.

      Files src/places.ts

    3. Journals get written on planes and in the countryside.

      Files src/entries.ts

    done when · tick each as it passes
    watch 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.
  4. Sync that keeps the promise

    A second device works without the contents becoming readable by anyone else.

    1. The sync store should be opaque even to whoever runs it.

      Files src/sync.ts

    2. The discarded version may be the only record of a day.

      Files src/conflicts.tstests/conflict.test.ts

    3. Media dominates and retransferring it is the slow path.

      Files src/sync.ts

    done when · tick each as it passes
    watch 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.
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more notes & knowledge.