Vibecode Day One
track this build4 phases, 12 steps, beginner friendly0%The visible digital journal loop is buildable, but a credible replacement needs more than the first screen. Day One earns its keep through polish, sync, importers, so expect a weekend or multi-day build and a narrower personal scope.
You are building a lean indie version of Day One. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Day One · indie build 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-day**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Encryption | A maintained library with parameters stored alongside the data | the journal must open years after the defaults changed | | Storage | Local database with encrypted media stored separately | photographs dominate the size and should not bloat the entry store | | Sync | Optional, end to end, through storage the user controls | a journal on someone else's server is a journal written differently | | Time | Entry time recorded with its timezone and location where given | an entry written while travelling belongs to where you were | | Export | Plain formats containing everything, round trip tested | this is how the writing outlives the software | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the local application. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **A maintained cryptography library** · free - 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 - [ ] **Several years of real journal entries** · free - 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 - [ ] **Storage you control for sync** (optional) · pennies 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 - [ ] **Your export format, chosen for readability in twenty years** · free - 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 ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - 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 If one of those is essential to you, that is the reason to keep paying for Day One, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Day One The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me an encrypted journal meant to still be readable in twenty years to replace Day One. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero A journal is different from other personal data in two ways that shape everything here. It is written on the assumption that nobody else will read it, which means the encryption is not a feature but the condition under which the writing is honest. If a user suspects the contents are readable by anyone else, they write differently, and the tool has quietly destroyed the thing it was for. And it is kept for decades. Most software this journal outlives will have changed format, changed company or stopped existing. So the export is not a checkbox: it is the mechanism by which twenty years of writing survives this application, and it has to be tested as a round trip on every release rather than assumed to work. Both of those point the same way. Encrypt with vetted primitives, store the parameters with the data, and make sure a plain export containing everything is one action away and provably complete. ### Stack (fixed, do not substitute) - Encryption: A maintained library with parameters stored alongside the data · the journal must open years after the defaults changed - Storage: Local database with encrypted media stored separately · photographs dominate the size and should not bloat the entry store - Sync: Optional, end to end, through storage the user controls · a journal on someone else's server is a journal written differently - Time: Entry time recorded with its timezone and location where given · an entry written while travelling belongs to where you were - Export: Plain formats containing everything, round trip tested · this is how the writing outlives the software ### Data model (create this before Phase 1) - 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. ### Phase 1 · Writing that is never lost Build: Entries with continuous local saving and a revision per writing session. Encrypt at rest with vetted primitives, storing the parameters with the journal. Verify a crash mid-word loses nothing. Trap: 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. Done when: A crash mid-sentence loses nothing, tested by killing the process while typing. The journal file records its encryption parameters and a journal created under old parameters opens after the defaults change. Revisions accumulate per writing session rather than per keystroke. The passphrase never reaches disk or a log. Do not build yet: No media, time handling, sync or export. ### Phase 2 · Photographs without filling the device Build: Store media encrypted outside the entry store with generated thumbnails. Bound the resident set so browsing a decade of entries does not load everything. Handle a video and a very large photograph. Trap: 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. Done when: A journal with several thousand photographs browses smoothly without loading full images. Media is encrypted and stored outside the entry database. A large video is handled or refused with a stated limit rather than failing. Thumbnails are generated once and reused. Do not build yet: No time handling, sync or export yet. ### Phase 3 · When and where you wrote it Build: Record the entry's instant with the timezone where it was written and an optional place. Display in that original timezone rather than the current one. Make location entirely optional and omittable per entry. Trap: 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. Done when: An entry written in another timezone displays in that zone regardless of where the device is now. Location is optional per entry and omitting it is a first-class choice rather than a blank field. A day boundary follows the entry's own zone. An entry created without a network still records its time correctly. Do not build yet: No sync, prompts or export yet. ### Phase 4 · Sync that keeps the promise Build: Optional end-to-end encrypted sync through storage the user controls. Resolve conflicts by keeping both versions of an entry. Ensure the sync store holds nothing readable, verified by inspecting what is transmitted. Trap: 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. Done when: The sync store contains nothing readable, verified by inspecting the transmitted and stored data. Entries edited on two devices produce both versions rather than one. Media syncs without transferring what the other device already has. Sync is optional and single-device use is complete. Do not build yet: No prompts, search or export yet. ### Phase 5 · An export that outlives the application Build: Export entries as plain files with metadata in front matter and media alongside. Verify the export by re-importing it and comparing entries, media and timestamps. Run that round trip in the test suite on every release. Trap: 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. Done when: Export produces plain readable files with metadata and media alongside. Re-importing reproduces entries, media, timestamps and timezones identically, asserted in the suite. The export is readable in a plain text editor without this application. Exporting a decade of entries completes without exhausting memory. Do not build yet: Nothing after this. ### Out of scope (and why) - Sharing entries with anyone, which changes what people are willing to write and is a different product. - Automatic content from other services, which sounds convenient and turns a journal into a log of your accounts. - Analysis of mood or sentiment across entries, which invites conclusions the data does not support about a person's life. - A hosted service, which makes you the custodian of other people's journals and is a responsibility worth declining. ### README must contain - That encryption parameters are stored with the journal, because it will be opened long after the defaults changed - That media lives outside the entry store, so browsing and backing up stay manageable over years - That entries display in the timezone where they were written rather than the device's current one - That sync conflicts keep both versions, since the discarded one may be the only record of a day - That the export round trip runs in the test suite on every release, because it is how the writing outlives this software ===== AGENTS.md ===== # Agent instructions · Day One indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: A maintained library with parameters stored alongside the data, Local database with encrypted media stored separately, Optional, end to end, through storage the user controls, Entry time recorded with its timezone and location where given, Plain formats containing everything, round trip tested. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - 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. - 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. - 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. - 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. ===== BUILD_PLAN.md ===== # Build plan · Day One 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. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Writing that is never lost An entry survives a crash mid-sentence and a decade of use. ### Steps 1. Save continuously, revise per session Two mechanisms. Per keystroke revisions make the history useless. Files: `src/entries.ts` 2. Store encryption parameters with the journal It will be opened after a long gap, by which time defaults have moved. Files: `src/crypto.ts` 3. Kill the process mid-word to test Not at a clean point. That is the case that matters. Files: `tests/recovery.test.ts` ### Done when - [ ] A crash mid-sentence loses nothing - [ ] An old-parameter journal opens after defaults change - [ ] Revisions accumulate per session - [ ] The passphrase never reaches disk or logs ### 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. ## Phase 2 · Photographs without filling the device Years of entries with media stay manageable on a normal machine. ### Steps 1. Store media outside the entry database Otherwise every read and every backup carries gigabytes. Files: `src/media.ts` 2. Generate thumbnails once Browsing a decade should not decode full images. Files: `src/thumbnails.ts` 3. Bound the resident set A timeline view must not load everything it scrolls past. Files: `src/timeline.ts` ### Done when - [ ] Thousands of photographs browse smoothly - [ ] Media is encrypted and stored separately - [ ] Large videos are handled or refused with a limit - [ ] Thumbnails are generated once and reused ### 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. ## Phase 3 · When and where you wrote it An entry written while travelling shows the time it was written there. ### Steps 1. Store the zone with the entry The time it was written there is the fact worth keeping. Files: `src/time.ts` 2. Make location omittable per entry Some entries should not record where you were. Files: `src/places.ts` 3. Work with no network Journals get written on planes and in the countryside. Files: `src/entries.ts` ### Done when - [ ] Entries display in their original timezone - [ ] Location is optional and omittable per entry - [ ] Day boundaries follow the entry's zone - [ ] Entries record correct times offline ### 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. ## Phase 4 · Sync that keeps the promise A second device works without the contents becoming readable by anyone else. ### Steps 1. Encrypt before it leaves the device The sync store should be opaque even to whoever runs it. Files: `src/sync.ts` 2. Keep both sides of a conflict The discarded version may be the only record of a day. Files: `src/conflicts.ts`, `tests/conflict.test.ts` 3. Transfer only what is missing Media dominates and retransferring it is the slow path. Files: `src/sync.ts` ### Done when - [ ] The sync store holds nothing readable - [ ] Concurrent edits keep both versions - [ ] Media transfers only what is missing - [ ] Single-device use is complete ### 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. ## Not in this build - Sharing entries with anyone. - Automatic content from other services. - Mood or sentiment analysis. - A hosted service. ## After v1, if you want it - 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 ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Encrypted entries. The most personal file the machine holds. JOURNAL_PATH=~/.local/share/journal/journal.db # Required. Encrypted media, stored outside the entry database. MEDIA_PATH=~/.local/share/journal/media # Required. Copied from current guidance and written into each journal it creates. KDF_PARAMS=argon2id:m=47104,t=1,p=1 # Required. Plain files. Somewhere your ordinary backups already reach. EXPORT_DIR=~/Documents/journal-export # Required. An unlocked journal on an unattended machine is the exposure. LOCK_IDLE_MINUTES=10 # Optional. Optional and end to end encrypted. Absent means one device, which works completely. SYNC_REMOTE=s3://my-journal/store
You are building a lean indie version of Day One. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Day One · indie build 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-day**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Encryption | A maintained library with parameters stored alongside the data | the journal must open years after the defaults changed | | Storage | Local database with encrypted media stored separately | photographs dominate the size and should not bloat the entry store | | Sync | Optional, end to end, through storage the user controls | a journal on someone else's server is a journal written differently | | Time | Entry time recorded with its timezone and location where given | an entry written while travelling belongs to where you were | | Export | Plain formats containing everything, round trip tested | this is how the writing outlives the software | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the local application. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **A maintained cryptography library** · free - 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 - [ ] **Several years of real journal entries** · free - 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 - [ ] **Storage you control for sync** (optional) · pennies 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 - [ ] **Your export format, chosen for readability in twenty years** · free - 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 ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - 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 If one of those is essential to you, that is the reason to keep paying for Day One, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Day One The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me an encrypted journal meant to still be readable in twenty years to replace Day One. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero A journal is different from other personal data in two ways that shape everything here. It is written on the assumption that nobody else will read it, which means the encryption is not a feature but the condition under which the writing is honest. If a user suspects the contents are readable by anyone else, they write differently, and the tool has quietly destroyed the thing it was for. And it is kept for decades. Most software this journal outlives will have changed format, changed company or stopped existing. So the export is not a checkbox: it is the mechanism by which twenty years of writing survives this application, and it has to be tested as a round trip on every release rather than assumed to work. Both of those point the same way. Encrypt with vetted primitives, store the parameters with the data, and make sure a plain export containing everything is one action away and provably complete. ### Stack (fixed, do not substitute) - Encryption: A maintained library with parameters stored alongside the data · the journal must open years after the defaults changed - Storage: Local database with encrypted media stored separately · photographs dominate the size and should not bloat the entry store - Sync: Optional, end to end, through storage the user controls · a journal on someone else's server is a journal written differently - Time: Entry time recorded with its timezone and location where given · an entry written while travelling belongs to where you were - Export: Plain formats containing everything, round trip tested · this is how the writing outlives the software ### Data model (create this before Phase 1) - 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. ### Phase 1 · Writing that is never lost Build: Entries with continuous local saving and a revision per writing session. Encrypt at rest with vetted primitives, storing the parameters with the journal. Verify a crash mid-word loses nothing. Trap: 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. Done when: A crash mid-sentence loses nothing, tested by killing the process while typing. The journal file records its encryption parameters and a journal created under old parameters opens after the defaults change. Revisions accumulate per writing session rather than per keystroke. The passphrase never reaches disk or a log. Do not build yet: No media, time handling, sync or export. ### Phase 2 · Photographs without filling the device Build: Store media encrypted outside the entry store with generated thumbnails. Bound the resident set so browsing a decade of entries does not load everything. Handle a video and a very large photograph. Trap: 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. Done when: A journal with several thousand photographs browses smoothly without loading full images. Media is encrypted and stored outside the entry database. A large video is handled or refused with a stated limit rather than failing. Thumbnails are generated once and reused. Do not build yet: No time handling, sync or export yet. ### Phase 3 · When and where you wrote it Build: Record the entry's instant with the timezone where it was written and an optional place. Display in that original timezone rather than the current one. Make location entirely optional and omittable per entry. Trap: 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. Done when: An entry written in another timezone displays in that zone regardless of where the device is now. Location is optional per entry and omitting it is a first-class choice rather than a blank field. A day boundary follows the entry's own zone. An entry created without a network still records its time correctly. Do not build yet: No sync, prompts or export yet. ### Phase 4 · Sync that keeps the promise Build: Optional end-to-end encrypted sync through storage the user controls. Resolve conflicts by keeping both versions of an entry. Ensure the sync store holds nothing readable, verified by inspecting what is transmitted. Trap: 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. Done when: The sync store contains nothing readable, verified by inspecting the transmitted and stored data. Entries edited on two devices produce both versions rather than one. Media syncs without transferring what the other device already has. Sync is optional and single-device use is complete. Do not build yet: No prompts, search or export yet. ### Phase 5 · An export that outlives the application Build: Export entries as plain files with metadata in front matter and media alongside. Verify the export by re-importing it and comparing entries, media and timestamps. Run that round trip in the test suite on every release. Trap: 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. Done when: Export produces plain readable files with metadata and media alongside. Re-importing reproduces entries, media, timestamps and timezones identically, asserted in the suite. The export is readable in a plain text editor without this application. Exporting a decade of entries completes without exhausting memory. Do not build yet: Nothing after this. ### Out of scope (and why) - Sharing entries with anyone, which changes what people are willing to write and is a different product. - Automatic content from other services, which sounds convenient and turns a journal into a log of your accounts. - Analysis of mood or sentiment across entries, which invites conclusions the data does not support about a person's life. - A hosted service, which makes you the custodian of other people's journals and is a responsibility worth declining. ### README must contain - That encryption parameters are stored with the journal, because it will be opened long after the defaults changed - That media lives outside the entry store, so browsing and backing up stay manageable over years - That entries display in the timezone where they were written rather than the device's current one - That sync conflicts keep both versions, since the discarded one may be the only record of a day - That the export round trip runs in the test suite on every release, because it is how the writing outlives this software ===== AGENTS.md ===== # Agent instructions · Day One indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: A maintained library with parameters stored alongside the data, Local database with encrypted media stored separately, Optional, end to end, through storage the user controls, Entry time recorded with its timezone and location where given, Plain formats containing everything, round trip tested. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - 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. - 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. - 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. - 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. ===== BUILD_PLAN.md ===== # Build plan · Day One 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. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Writing that is never lost An entry survives a crash mid-sentence and a decade of use. ### Steps 1. Save continuously, revise per session Two mechanisms. Per keystroke revisions make the history useless. Files: `src/entries.ts` 2. Store encryption parameters with the journal It will be opened after a long gap, by which time defaults have moved. Files: `src/crypto.ts` 3. Kill the process mid-word to test Not at a clean point. That is the case that matters. Files: `tests/recovery.test.ts` ### Done when - [ ] A crash mid-sentence loses nothing - [ ] An old-parameter journal opens after defaults change - [ ] Revisions accumulate per session - [ ] The passphrase never reaches disk or logs ### 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. ## Phase 2 · Photographs without filling the device Years of entries with media stay manageable on a normal machine. ### Steps 1. Store media outside the entry database Otherwise every read and every backup carries gigabytes. Files: `src/media.ts` 2. Generate thumbnails once Browsing a decade should not decode full images. Files: `src/thumbnails.ts` 3. Bound the resident set A timeline view must not load everything it scrolls past. Files: `src/timeline.ts` ### Done when - [ ] Thousands of photographs browse smoothly - [ ] Media is encrypted and stored separately - [ ] Large videos are handled or refused with a limit - [ ] Thumbnails are generated once and reused ### 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. ## Phase 3 · When and where you wrote it An entry written while travelling shows the time it was written there. ### Steps 1. Store the zone with the entry The time it was written there is the fact worth keeping. Files: `src/time.ts` 2. Make location omittable per entry Some entries should not record where you were. Files: `src/places.ts` 3. Work with no network Journals get written on planes and in the countryside. Files: `src/entries.ts` ### Done when - [ ] Entries display in their original timezone - [ ] Location is optional and omittable per entry - [ ] Day boundaries follow the entry's zone - [ ] Entries record correct times offline ### 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. ## Phase 4 · Sync that keeps the promise A second device works without the contents becoming readable by anyone else. ### Steps 1. Encrypt before it leaves the device The sync store should be opaque even to whoever runs it. Files: `src/sync.ts` 2. Keep both sides of a conflict The discarded version may be the only record of a day. Files: `src/conflicts.ts`, `tests/conflict.test.ts` 3. Transfer only what is missing Media dominates and retransferring it is the slow path. Files: `src/sync.ts` ### Done when - [ ] The sync store holds nothing readable - [ ] Concurrent edits keep both versions - [ ] Media transfers only what is missing - [ ] Single-device use is complete ### 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. ## Not in this build - Sharing entries with anyone. - Automatic content from other services. - Mood or sentiment analysis. - A hosted service. ## After v1, if you want it - 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 ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Encrypted entries. The most personal file the machine holds. JOURNAL_PATH=~/.local/share/journal/journal.db # Required. Encrypted media, stored outside the entry database. MEDIA_PATH=~/.local/share/journal/media # Required. Copied from current guidance and written into each journal it creates. KDF_PARAMS=argon2id:m=47104,t=1,p=1 # Required. Plain files. Somewhere your ordinary backups already reach. EXPORT_DIR=~/Documents/journal-export # Required. An unlocked journal on an unattended machine is the exposure. LOCK_IDLE_MINUTES=10 # Optional. Optional and end to end encrypted. Absent means one device, which works completely. SYNC_REMOTE=s3://my-journal/store
You are building a production product version of Day One. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== PRODUCT.md ===== # Day One · product brief ## Problem The visible digital journal loop is buildable, but a credible replacement needs more than the first screen. Day One earns its keep through polish, sync, importers, so expect a weekend or multi-day build and a narrower personal scope. ## Product outcome A journal private enough to write honestly in, fast enough after a decade of photographs, and exportable into plain files that will still open when this application is long gone. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - desktop development machine - Node.js 22 and Rust toolchain - local filesystem access ## Explicit non-goals for v1 - 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 ## Success criteria - A crash mid-sentence is confirmed to lose nothing - An old-parameter journal is confirmed to open after defaults change - Entries are confirmed to display in their original timezone - A sync conflict is confirmed to keep both versions - The export round trip is confirmed identical in the suite ===== BRIEF.md ===== # Build brief · Day One The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me an encrypted journal meant to still be readable in twenty years to replace Day One. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero A journal is different from other personal data in two ways that shape everything here. It is written on the assumption that nobody else will read it, which means the encryption is not a feature but the condition under which the writing is honest. If a user suspects the contents are readable by anyone else, they write differently, and the tool has quietly destroyed the thing it was for. And it is kept for decades. Most software this journal outlives will have changed format, changed company or stopped existing. So the export is not a checkbox: it is the mechanism by which twenty years of writing survives this application, and it has to be tested as a round trip on every release rather than assumed to work. Both of those point the same way. Encrypt with vetted primitives, store the parameters with the data, and make sure a plain export containing everything is one action away and provably complete. ### Stack (fixed, do not substitute) - Encryption: A maintained library with parameters stored alongside the data · the journal must open years after the defaults changed - Storage: Local database with encrypted media stored separately · photographs dominate the size and should not bloat the entry store - Sync: Optional, end to end, through storage the user controls · a journal on someone else's server is a journal written differently - Time: Entry time recorded with its timezone and location where given · an entry written while travelling belongs to where you were - Export: Plain formats containing everything, round trip tested · this is how the writing outlives the software ### Data model (create this before Phase 1) - 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. ### Phase 1 · Writing that is never lost Build: Entries with continuous local saving and a revision per writing session. Encrypt at rest with vetted primitives, storing the parameters with the journal. Verify a crash mid-word loses nothing. Trap: 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. Done when: A crash mid-sentence loses nothing, tested by killing the process while typing. The journal file records its encryption parameters and a journal created under old parameters opens after the defaults change. Revisions accumulate per writing session rather than per keystroke. The passphrase never reaches disk or a log. Do not build yet: No media, time handling, sync or export. ### Phase 2 · Photographs without filling the device Build: Store media encrypted outside the entry store with generated thumbnails. Bound the resident set so browsing a decade of entries does not load everything. Handle a video and a very large photograph. Trap: 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. Done when: A journal with several thousand photographs browses smoothly without loading full images. Media is encrypted and stored outside the entry database. A large video is handled or refused with a stated limit rather than failing. Thumbnails are generated once and reused. Do not build yet: No time handling, sync or export yet. ### Phase 3 · When and where you wrote it Build: Record the entry's instant with the timezone where it was written and an optional place. Display in that original timezone rather than the current one. Make location entirely optional and omittable per entry. Trap: 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. Done when: An entry written in another timezone displays in that zone regardless of where the device is now. Location is optional per entry and omitting it is a first-class choice rather than a blank field. A day boundary follows the entry's own zone. An entry created without a network still records its time correctly. Do not build yet: No sync, prompts or export yet. ### Phase 4 · Sync that keeps the promise Build: Optional end-to-end encrypted sync through storage the user controls. Resolve conflicts by keeping both versions of an entry. Ensure the sync store holds nothing readable, verified by inspecting what is transmitted. Trap: 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. Done when: The sync store contains nothing readable, verified by inspecting the transmitted and stored data. Entries edited on two devices produce both versions rather than one. Media syncs without transferring what the other device already has. Sync is optional and single-device use is complete. Do not build yet: No prompts, search or export yet. ### Phase 5 · An export that outlives the application Build: Export entries as plain files with metadata in front matter and media alongside. Verify the export by re-importing it and comparing entries, media and timestamps. Run that round trip in the test suite on every release. Trap: 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. Done when: Export produces plain readable files with metadata and media alongside. Re-importing reproduces entries, media, timestamps and timezones identically, asserted in the suite. The export is readable in a plain text editor without this application. Exporting a decade of entries completes without exhausting memory. Do not build yet: Nothing after this. ### Out of scope (and why) - Sharing entries with anyone, which changes what people are willing to write and is a different product. - Automatic content from other services, which sounds convenient and turns a journal into a log of your accounts. - Analysis of mood or sentiment across entries, which invites conclusions the data does not support about a person's life. - A hosted service, which makes you the custodian of other people's journals and is a responsibility worth declining. ### README must contain - That encryption parameters are stored with the journal, because it will be opened long after the defaults changed - That media lives outside the entry store, so browsing and backing up stay manageable over years - That entries display in the timezone where they were written rather than the device's current one - That sync conflicts keep both versions, since the discarded one may be the only record of a day - That the export round trip runs in the test suite on every release, because it is how the writing outlives this software ===== ARCHITECTURE.md ===== # Architecture · Day One ## Stack | Part | Choice | Why | | --- | --- | --- | | Encryption | A maintained library with parameters stored alongside the data | the journal must open years after the defaults changed | | Storage | Local database with encrypted media stored separately | photographs dominate the size and should not bloat the entry store | | Sync | Optional, end to end, through storage the user controls | a journal on someone else's server is a journal written differently | | Time | Entry time recorded with its timezone and location where given | an entry written while travelling belongs to where you were | | Export | Plain formats containing everything, round trip tested | this is how the writing outlives the software | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Encryption | Vetted primitives with parameters stored per journal. | Any maintained library. Never a construction of your own. | | Media | Encrypted storage outside the entry database. | Any store, provided entries stay small and browsable. | | Time | Original timezone per entry. | Any implementation, provided display uses the entry's zone. | | Sync | End-to-end encrypted, conflicts retained. | Any transport, provided the store holds nothing readable. | | Export | Plain formats with a tested round trip. | Not swappable for a proprietary format, which is how writing gets stranded. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `JOURNAL_PATH` · required · Encrypted entries. The most personal file the machine holds. - `MEDIA_PATH` · required · Encrypted media, stored outside the entry database. - `KDF_PARAMS` · required · Copied from current guidance and written into each journal it creates. - `EXPORT_DIR` · required · Plain files. Somewhere your ordinary backups already reach. - `LOCK_IDLE_MINUTES` · required · An unlocked journal on an unattended machine is the exposure. - `SYNC_REMOTE` · optional · Optional and end to end encrypted. Absent means one device, which works completely. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it. ===== AGENTS.md ===== # Agent instructions · Day One product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: A maintained library with parameters stored alongside the data, Local database with encrypted media stored separately, Optional, end to end, through storage the user controls, Entry time recorded with its timezone and location where given, Plain formats containing everything, round trip tested. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - 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. - 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. - 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. - 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 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. ===== MILESTONES.md ===== # Delivery milestones · Day One Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Writing that is never lost An entry survives a crash mid-sentence and a decade of use. ### Steps 1. Save continuously, revise per session Two mechanisms. Per keystroke revisions make the history useless. Files: `src/entries.ts` 2. Store encryption parameters with the journal It will be opened after a long gap, by which time defaults have moved. Files: `src/crypto.ts` 3. Kill the process mid-word to test Not at a clean point. That is the case that matters. Files: `tests/recovery.test.ts` ### Done when - [ ] A crash mid-sentence loses nothing - [ ] An old-parameter journal opens after defaults change - [ ] Revisions accumulate per session - [ ] The passphrase never reaches disk or logs ### 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. ## M2 · Photographs without filling the device Years of entries with media stay manageable on a normal machine. ### Steps 1. Store media outside the entry database Otherwise every read and every backup carries gigabytes. Files: `src/media.ts` 2. Generate thumbnails once Browsing a decade should not decode full images. Files: `src/thumbnails.ts` 3. Bound the resident set A timeline view must not load everything it scrolls past. Files: `src/timeline.ts` ### Done when - [ ] Thousands of photographs browse smoothly - [ ] Media is encrypted and stored separately - [ ] Large videos are handled or refused with a limit - [ ] Thumbnails are generated once and reused ### 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. ## M3 · When and where you wrote it An entry written while travelling shows the time it was written there. ### Steps 1. Store the zone with the entry The time it was written there is the fact worth keeping. Files: `src/time.ts` 2. Make location omittable per entry Some entries should not record where you were. Files: `src/places.ts` 3. Work with no network Journals get written on planes and in the countryside. Files: `src/entries.ts` ### Done when - [ ] Entries display in their original timezone - [ ] Location is optional and omittable per entry - [ ] Day boundaries follow the entry's zone - [ ] Entries record correct times offline ### 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. ## M4 · Sync that keeps the promise A second device works without the contents becoming readable by anyone else. ### Steps 1. Encrypt before it leaves the device The sync store should be opaque even to whoever runs it. Files: `src/sync.ts` 2. Keep both sides of a conflict The discarded version may be the only record of a day. Files: `src/conflicts.ts`, `tests/conflict.test.ts` 3. Transfer only what is missing Media dominates and retransferring it is the slow path. Files: `src/sync.ts` ### Done when - [ ] The sync store holds nothing readable - [ ] Concurrent edits keep both versions - [ ] Media transfers only what is missing - [ ] Single-device use is complete ### 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. ## M5 · An export that outlives the application (production only) Everything leaves in a format readable without this software. ### Steps 1. Export plain files with front matter Readable in a text editor in twenty years, with no software required. Files: `src/export.ts` 2. Round trip in the suite every release This is the mechanism by which the writing survives the software. Files: `tests/roundtrip.test.ts` 3. Stream large exports A decade of entries should not need a decade of memory. Files: `src/export.ts` ### Done when - [ ] Export produces plain readable files with media - [ ] The round trip reproduces everything identically - [ ] The export reads in a plain text editor - [ ] A decade exports without memory exhaustion ### Watch 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. ===== OPERATIONS.md ===== # Operations · Day One ## Backup A journal is among the least replaceable things a person owns. Back up the encrypted store and the media together, keep an occasional plain export as a format-independent copy, and store the passphrase somewhere it will survive you if that matters to you. ## Restore Restore and confirm the journal opens with its recorded parameters and that media references resolve, since a restore returning entries without media has lost half of many of them. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring The export round trip in the suite is the check that matters, since it is the only guarantee that the writing survives the software. It should run on every release rather than occasionally. ## Incident checklist An unopenable journal is the incident, and after a long gap the most likely cause is a parameter change or a lost passphrase rather than corruption. Storing parameters with the journal removes the first; only the user can address the second, which is worth telling them clearly. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] A crash mid-sentence is confirmed to lose nothing - [ ] An old-parameter journal is confirmed to open after defaults change - [ ] Entries are confirmed to display in their original timezone - [ ] A sync conflict is confirmed to keep both versions - [ ] The export round trip is confirmed identical in the suite ## Launch constraint Do not market omitted Day One capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered. ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Encrypted entries. The most personal file the machine holds. JOURNAL_PATH=~/.local/share/journal/journal.db # Required. Encrypted media, stored outside the entry database. MEDIA_PATH=~/.local/share/journal/media # Required. Copied from current guidance and written into each journal it creates. KDF_PARAMS=argon2id:m=47104,t=1,p=1 # Required. Plain files. Somewhere your ordinary backups already reach. EXPORT_DIR=~/Documents/journal-export # Required. An unlocked journal on an unattended machine is the exposure. LOCK_IDLE_MINUTES=10 # Optional. Optional and end to end encrypted. Absent means one device, which works completely. SYNC_REMOTE=s3://my-journal/store
# Day One · indie build 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-day**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Encryption | A maintained library with parameters stored alongside the data | the journal must open years after the defaults changed | | Storage | Local database with encrypted media stored separately | photographs dominate the size and should not bloat the entry store | | Sync | Optional, end to end, through storage the user controls | a journal on someone else's server is a journal written differently | | Time | Entry time recorded with its timezone and location where given | an entry written while travelling belongs to where you were | | Export | Plain formats containing everything, round trip tested | this is how the writing outlives the software | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the local application. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **A maintained cryptography library** · free - 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 - [ ] **Several years of real journal entries** · free - 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 - [ ] **Storage you control for sync** (optional) · pennies 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 - [ ] **Your export format, chosen for readability in twenty years** · free - 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 ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - 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 If one of those is essential to you, that is the reason to keep paying for Day One, and the README should say so rather than pretend.
# Build brief · Day One The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me an encrypted journal meant to still be readable in twenty years to replace Day One. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero A journal is different from other personal data in two ways that shape everything here. It is written on the assumption that nobody else will read it, which means the encryption is not a feature but the condition under which the writing is honest. If a user suspects the contents are readable by anyone else, they write differently, and the tool has quietly destroyed the thing it was for. And it is kept for decades. Most software this journal outlives will have changed format, changed company or stopped existing. So the export is not a checkbox: it is the mechanism by which twenty years of writing survives this application, and it has to be tested as a round trip on every release rather than assumed to work. Both of those point the same way. Encrypt with vetted primitives, store the parameters with the data, and make sure a plain export containing everything is one action away and provably complete. ### Stack (fixed, do not substitute) - Encryption: A maintained library with parameters stored alongside the data · the journal must open years after the defaults changed - Storage: Local database with encrypted media stored separately · photographs dominate the size and should not bloat the entry store - Sync: Optional, end to end, through storage the user controls · a journal on someone else's server is a journal written differently - Time: Entry time recorded with its timezone and location where given · an entry written while travelling belongs to where you were - Export: Plain formats containing everything, round trip tested · this is how the writing outlives the software ### Data model (create this before Phase 1) - 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. ### Phase 1 · Writing that is never lost Build: Entries with continuous local saving and a revision per writing session. Encrypt at rest with vetted primitives, storing the parameters with the journal. Verify a crash mid-word loses nothing. Trap: 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. Done when: A crash mid-sentence loses nothing, tested by killing the process while typing. The journal file records its encryption parameters and a journal created under old parameters opens after the defaults change. Revisions accumulate per writing session rather than per keystroke. The passphrase never reaches disk or a log. Do not build yet: No media, time handling, sync or export. ### Phase 2 · Photographs without filling the device Build: Store media encrypted outside the entry store with generated thumbnails. Bound the resident set so browsing a decade of entries does not load everything. Handle a video and a very large photograph. Trap: 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. Done when: A journal with several thousand photographs browses smoothly without loading full images. Media is encrypted and stored outside the entry database. A large video is handled or refused with a stated limit rather than failing. Thumbnails are generated once and reused. Do not build yet: No time handling, sync or export yet. ### Phase 3 · When and where you wrote it Build: Record the entry's instant with the timezone where it was written and an optional place. Display in that original timezone rather than the current one. Make location entirely optional and omittable per entry. Trap: 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. Done when: An entry written in another timezone displays in that zone regardless of where the device is now. Location is optional per entry and omitting it is a first-class choice rather than a blank field. A day boundary follows the entry's own zone. An entry created without a network still records its time correctly. Do not build yet: No sync, prompts or export yet. ### Phase 4 · Sync that keeps the promise Build: Optional end-to-end encrypted sync through storage the user controls. Resolve conflicts by keeping both versions of an entry. Ensure the sync store holds nothing readable, verified by inspecting what is transmitted. Trap: 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. Done when: The sync store contains nothing readable, verified by inspecting the transmitted and stored data. Entries edited on two devices produce both versions rather than one. Media syncs without transferring what the other device already has. Sync is optional and single-device use is complete. Do not build yet: No prompts, search or export yet. ### Phase 5 · An export that outlives the application Build: Export entries as plain files with metadata in front matter and media alongside. Verify the export by re-importing it and comparing entries, media and timestamps. Run that round trip in the test suite on every release. Trap: 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. Done when: Export produces plain readable files with metadata and media alongside. Re-importing reproduces entries, media, timestamps and timezones identically, asserted in the suite. The export is readable in a plain text editor without this application. Exporting a decade of entries completes without exhausting memory. Do not build yet: Nothing after this. ### Out of scope (and why) - Sharing entries with anyone, which changes what people are willing to write and is a different product. - Automatic content from other services, which sounds convenient and turns a journal into a log of your accounts. - Analysis of mood or sentiment across entries, which invites conclusions the data does not support about a person's life. - A hosted service, which makes you the custodian of other people's journals and is a responsibility worth declining. ### README must contain - That encryption parameters are stored with the journal, because it will be opened long after the defaults changed - That media lives outside the entry store, so browsing and backing up stay manageable over years - That entries display in the timezone where they were written rather than the device's current one - That sync conflicts keep both versions, since the discarded one may be the only record of a day - That the export round trip runs in the test suite on every release, because it is how the writing outlives this software
# Agent instructions · Day One indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: A maintained library with parameters stored alongside the data, Local database with encrypted media stored separately, Optional, end to end, through storage the user controls, Entry time recorded with its timezone and location where given, Plain formats containing everything, round trip tested. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - 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. - 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. - 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. - 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.
# Build plan · Day One 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. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Writing that is never lost An entry survives a crash mid-sentence and a decade of use. ### Steps 1. Save continuously, revise per session Two mechanisms. Per keystroke revisions make the history useless. Files: `src/entries.ts` 2. Store encryption parameters with the journal It will be opened after a long gap, by which time defaults have moved. Files: `src/crypto.ts` 3. Kill the process mid-word to test Not at a clean point. That is the case that matters. Files: `tests/recovery.test.ts` ### Done when - [ ] A crash mid-sentence loses nothing - [ ] An old-parameter journal opens after defaults change - [ ] Revisions accumulate per session - [ ] The passphrase never reaches disk or logs ### 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. ## Phase 2 · Photographs without filling the device Years of entries with media stay manageable on a normal machine. ### Steps 1. Store media outside the entry database Otherwise every read and every backup carries gigabytes. Files: `src/media.ts` 2. Generate thumbnails once Browsing a decade should not decode full images. Files: `src/thumbnails.ts` 3. Bound the resident set A timeline view must not load everything it scrolls past. Files: `src/timeline.ts` ### Done when - [ ] Thousands of photographs browse smoothly - [ ] Media is encrypted and stored separately - [ ] Large videos are handled or refused with a limit - [ ] Thumbnails are generated once and reused ### 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. ## Phase 3 · When and where you wrote it An entry written while travelling shows the time it was written there. ### Steps 1. Store the zone with the entry The time it was written there is the fact worth keeping. Files: `src/time.ts` 2. Make location omittable per entry Some entries should not record where you were. Files: `src/places.ts` 3. Work with no network Journals get written on planes and in the countryside. Files: `src/entries.ts` ### Done when - [ ] Entries display in their original timezone - [ ] Location is optional and omittable per entry - [ ] Day boundaries follow the entry's zone - [ ] Entries record correct times offline ### 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. ## Phase 4 · Sync that keeps the promise A second device works without the contents becoming readable by anyone else. ### Steps 1. Encrypt before it leaves the device The sync store should be opaque even to whoever runs it. Files: `src/sync.ts` 2. Keep both sides of a conflict The discarded version may be the only record of a day. Files: `src/conflicts.ts`, `tests/conflict.test.ts` 3. Transfer only what is missing Media dominates and retransferring it is the slow path. Files: `src/sync.ts` ### Done when - [ ] The sync store holds nothing readable - [ ] Concurrent edits keep both versions - [ ] Media transfers only what is missing - [ ] Single-device use is complete ### 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. ## Not in this build - Sharing entries with anyone. - Automatic content from other services. - Mood or sentiment analysis. - A hosted service. ## After v1, if you want it - 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
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Encrypted entries. The most personal file the machine holds. JOURNAL_PATH=~/.local/share/journal/journal.db # Required. Encrypted media, stored outside the entry database. MEDIA_PATH=~/.local/share/journal/media # Required. Copied from current guidance and written into each journal it creates. KDF_PARAMS=argon2id:m=47104,t=1,p=1 # Required. Plain files. Somewhere your ordinary backups already reach. EXPORT_DIR=~/Documents/journal-export # Required. An unlocked journal on an unattended machine is the exposure. LOCK_IDLE_MINUTES=10 # Optional. Optional and end to end encrypted. Absent means one device, which works completely. SYNC_REMOTE=s3://my-journal/store
# Day One · product brief ## Problem The visible digital journal loop is buildable, but a credible replacement needs more than the first screen. Day One earns its keep through polish, sync, importers, so expect a weekend or multi-day build and a narrower personal scope. ## Product outcome A journal private enough to write honestly in, fast enough after a decade of photographs, and exportable into plain files that will still open when this application is long gone. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - desktop development machine - Node.js 22 and Rust toolchain - local filesystem access ## Explicit non-goals for v1 - 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 ## Success criteria - A crash mid-sentence is confirmed to lose nothing - An old-parameter journal is confirmed to open after defaults change - Entries are confirmed to display in their original timezone - A sync conflict is confirmed to keep both versions - The export round trip is confirmed identical in the suite
# Build brief · Day One The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me an encrypted journal meant to still be readable in twenty years to replace Day One. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero A journal is different from other personal data in two ways that shape everything here. It is written on the assumption that nobody else will read it, which means the encryption is not a feature but the condition under which the writing is honest. If a user suspects the contents are readable by anyone else, they write differently, and the tool has quietly destroyed the thing it was for. And it is kept for decades. Most software this journal outlives will have changed format, changed company or stopped existing. So the export is not a checkbox: it is the mechanism by which twenty years of writing survives this application, and it has to be tested as a round trip on every release rather than assumed to work. Both of those point the same way. Encrypt with vetted primitives, store the parameters with the data, and make sure a plain export containing everything is one action away and provably complete. ### Stack (fixed, do not substitute) - Encryption: A maintained library with parameters stored alongside the data · the journal must open years after the defaults changed - Storage: Local database with encrypted media stored separately · photographs dominate the size and should not bloat the entry store - Sync: Optional, end to end, through storage the user controls · a journal on someone else's server is a journal written differently - Time: Entry time recorded with its timezone and location where given · an entry written while travelling belongs to where you were - Export: Plain formats containing everything, round trip tested · this is how the writing outlives the software ### Data model (create this before Phase 1) - 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. ### Phase 1 · Writing that is never lost Build: Entries with continuous local saving and a revision per writing session. Encrypt at rest with vetted primitives, storing the parameters with the journal. Verify a crash mid-word loses nothing. Trap: 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. Done when: A crash mid-sentence loses nothing, tested by killing the process while typing. The journal file records its encryption parameters and a journal created under old parameters opens after the defaults change. Revisions accumulate per writing session rather than per keystroke. The passphrase never reaches disk or a log. Do not build yet: No media, time handling, sync or export. ### Phase 2 · Photographs without filling the device Build: Store media encrypted outside the entry store with generated thumbnails. Bound the resident set so browsing a decade of entries does not load everything. Handle a video and a very large photograph. Trap: 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. Done when: A journal with several thousand photographs browses smoothly without loading full images. Media is encrypted and stored outside the entry database. A large video is handled or refused with a stated limit rather than failing. Thumbnails are generated once and reused. Do not build yet: No time handling, sync or export yet. ### Phase 3 · When and where you wrote it Build: Record the entry's instant with the timezone where it was written and an optional place. Display in that original timezone rather than the current one. Make location entirely optional and omittable per entry. Trap: 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. Done when: An entry written in another timezone displays in that zone regardless of where the device is now. Location is optional per entry and omitting it is a first-class choice rather than a blank field. A day boundary follows the entry's own zone. An entry created without a network still records its time correctly. Do not build yet: No sync, prompts or export yet. ### Phase 4 · Sync that keeps the promise Build: Optional end-to-end encrypted sync through storage the user controls. Resolve conflicts by keeping both versions of an entry. Ensure the sync store holds nothing readable, verified by inspecting what is transmitted. Trap: 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. Done when: The sync store contains nothing readable, verified by inspecting the transmitted and stored data. Entries edited on two devices produce both versions rather than one. Media syncs without transferring what the other device already has. Sync is optional and single-device use is complete. Do not build yet: No prompts, search or export yet. ### Phase 5 · An export that outlives the application Build: Export entries as plain files with metadata in front matter and media alongside. Verify the export by re-importing it and comparing entries, media and timestamps. Run that round trip in the test suite on every release. Trap: 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. Done when: Export produces plain readable files with metadata and media alongside. Re-importing reproduces entries, media, timestamps and timezones identically, asserted in the suite. The export is readable in a plain text editor without this application. Exporting a decade of entries completes without exhausting memory. Do not build yet: Nothing after this. ### Out of scope (and why) - Sharing entries with anyone, which changes what people are willing to write and is a different product. - Automatic content from other services, which sounds convenient and turns a journal into a log of your accounts. - Analysis of mood or sentiment across entries, which invites conclusions the data does not support about a person's life. - A hosted service, which makes you the custodian of other people's journals and is a responsibility worth declining. ### README must contain - That encryption parameters are stored with the journal, because it will be opened long after the defaults changed - That media lives outside the entry store, so browsing and backing up stay manageable over years - That entries display in the timezone where they were written rather than the device's current one - That sync conflicts keep both versions, since the discarded one may be the only record of a day - That the export round trip runs in the test suite on every release, because it is how the writing outlives this software
# Architecture · Day One ## Stack | Part | Choice | Why | | --- | --- | --- | | Encryption | A maintained library with parameters stored alongside the data | the journal must open years after the defaults changed | | Storage | Local database with encrypted media stored separately | photographs dominate the size and should not bloat the entry store | | Sync | Optional, end to end, through storage the user controls | a journal on someone else's server is a journal written differently | | Time | Entry time recorded with its timezone and location where given | an entry written while travelling belongs to where you were | | Export | Plain formats containing everything, round trip tested | this is how the writing outlives the software | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Encryption | Vetted primitives with parameters stored per journal. | Any maintained library. Never a construction of your own. | | Media | Encrypted storage outside the entry database. | Any store, provided entries stay small and browsable. | | Time | Original timezone per entry. | Any implementation, provided display uses the entry's zone. | | Sync | End-to-end encrypted, conflicts retained. | Any transport, provided the store holds nothing readable. | | Export | Plain formats with a tested round trip. | Not swappable for a proprietary format, which is how writing gets stranded. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `JOURNAL_PATH` · required · Encrypted entries. The most personal file the machine holds. - `MEDIA_PATH` · required · Encrypted media, stored outside the entry database. - `KDF_PARAMS` · required · Copied from current guidance and written into each journal it creates. - `EXPORT_DIR` · required · Plain files. Somewhere your ordinary backups already reach. - `LOCK_IDLE_MINUTES` · required · An unlocked journal on an unattended machine is the exposure. - `SYNC_REMOTE` · optional · Optional and end to end encrypted. Absent means one device, which works completely. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it.
# Agent instructions · Day One product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: A maintained library with parameters stored alongside the data, Local database with encrypted media stored separately, Optional, end to end, through storage the user controls, Entry time recorded with its timezone and location where given, Plain formats containing everything, round trip tested. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - 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. - 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. - 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. - 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 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.
# Delivery milestones · Day One Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Writing that is never lost An entry survives a crash mid-sentence and a decade of use. ### Steps 1. Save continuously, revise per session Two mechanisms. Per keystroke revisions make the history useless. Files: `src/entries.ts` 2. Store encryption parameters with the journal It will be opened after a long gap, by which time defaults have moved. Files: `src/crypto.ts` 3. Kill the process mid-word to test Not at a clean point. That is the case that matters. Files: `tests/recovery.test.ts` ### Done when - [ ] A crash mid-sentence loses nothing - [ ] An old-parameter journal opens after defaults change - [ ] Revisions accumulate per session - [ ] The passphrase never reaches disk or logs ### 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. ## M2 · Photographs without filling the device Years of entries with media stay manageable on a normal machine. ### Steps 1. Store media outside the entry database Otherwise every read and every backup carries gigabytes. Files: `src/media.ts` 2. Generate thumbnails once Browsing a decade should not decode full images. Files: `src/thumbnails.ts` 3. Bound the resident set A timeline view must not load everything it scrolls past. Files: `src/timeline.ts` ### Done when - [ ] Thousands of photographs browse smoothly - [ ] Media is encrypted and stored separately - [ ] Large videos are handled or refused with a limit - [ ] Thumbnails are generated once and reused ### 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. ## M3 · When and where you wrote it An entry written while travelling shows the time it was written there. ### Steps 1. Store the zone with the entry The time it was written there is the fact worth keeping. Files: `src/time.ts` 2. Make location omittable per entry Some entries should not record where you were. Files: `src/places.ts` 3. Work with no network Journals get written on planes and in the countryside. Files: `src/entries.ts` ### Done when - [ ] Entries display in their original timezone - [ ] Location is optional and omittable per entry - [ ] Day boundaries follow the entry's zone - [ ] Entries record correct times offline ### 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. ## M4 · Sync that keeps the promise A second device works without the contents becoming readable by anyone else. ### Steps 1. Encrypt before it leaves the device The sync store should be opaque even to whoever runs it. Files: `src/sync.ts` 2. Keep both sides of a conflict The discarded version may be the only record of a day. Files: `src/conflicts.ts`, `tests/conflict.test.ts` 3. Transfer only what is missing Media dominates and retransferring it is the slow path. Files: `src/sync.ts` ### Done when - [ ] The sync store holds nothing readable - [ ] Concurrent edits keep both versions - [ ] Media transfers only what is missing - [ ] Single-device use is complete ### 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. ## M5 · An export that outlives the application (production only) Everything leaves in a format readable without this software. ### Steps 1. Export plain files with front matter Readable in a text editor in twenty years, with no software required. Files: `src/export.ts` 2. Round trip in the suite every release This is the mechanism by which the writing survives the software. Files: `tests/roundtrip.test.ts` 3. Stream large exports A decade of entries should not need a decade of memory. Files: `src/export.ts` ### Done when - [ ] Export produces plain readable files with media - [ ] The round trip reproduces everything identically - [ ] The export reads in a plain text editor - [ ] A decade exports without memory exhaustion ### Watch 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.
# Operations · Day One ## Backup A journal is among the least replaceable things a person owns. Back up the encrypted store and the media together, keep an occasional plain export as a format-independent copy, and store the passphrase somewhere it will survive you if that matters to you. ## Restore Restore and confirm the journal opens with its recorded parameters and that media references resolve, since a restore returning entries without media has lost half of many of them. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring The export round trip in the suite is the check that matters, since it is the only guarantee that the writing survives the software. It should run on every release rather than occasionally. ## Incident checklist An unopenable journal is the incident, and after a long gap the most likely cause is a parameter change or a lost passphrase rather than corruption. Storing parameters with the journal removes the first; only the user can address the second, which is worth telling them clearly. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] A crash mid-sentence is confirmed to lose nothing - [ ] An old-parameter journal is confirmed to open after defaults change - [ ] Entries are confirmed to display in their original timezone - [ ] A sync conflict is confirmed to keep both versions - [ ] The export round trip is confirmed identical in the suite ## Launch constraint Do not market omitted Day One capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered.
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Encrypted entries. The most personal file the machine holds. JOURNAL_PATH=~/.local/share/journal/journal.db # Required. Encrypted media, stored outside the entry database. MEDIA_PATH=~/.local/share/journal/media # Required. Copied from current guidance and written into each journal it creates. KDF_PARAMS=argon2id:m=47104,t=1,p=1 # Required. Plain files. Somewhere your ordinary backups already reach. EXPORT_DIR=~/Documents/journal-export # Required. An unlocked journal on an unattended machine is the exposure. LOCK_IDLE_MINUTES=10 # Optional. Optional and end to end encrypted. Absent means one device, which works completely. SYNC_REMOTE=s3://my-journal/store
$ choose a build depth, inspect the files, then open the complete pack in your agent
Day One: People pay for a writing surface they trust for years, plus migration tools, sync, and tiny interaction details that disappear into habit.
xconflict-safe multi-device sync
xmature importers and exporters
xcollaborative editing and sharing
xnative mobile clients
Don't feel like building it? These folks already made it free.
no votes, no pay-to-list · just what's real
Day One pricing
| plan | monthly | annual (per mo) | what you get |
|---|---|---|---|
| basic | $0/user | $0/user | Unlimited text entries and journals; 1 device; 1 photo/entry; web access, shared journals and exports |
| silver | — | $4.17/user | Unlimited devices; up to 30 photos/videos per entry; audio/transcription, PDF scans, integrations; 25% print discount |
| gold | — | $6.25/user | Silver plus reflective AI, daily chat, prompts/summaries/title suggestions/image generation; 35% print discount |
free tierUnlimited text entries and journals; 1 device; 1 photo per entry
billingannual only for Silver and Gold; 1-month in-app trial; no public monthly plan
hidden costsGold AI has daily fair-use limits, but Day One does not publish the numbers. Trial eligibility and billing changes are controlled by the original app-store platform.
verified 2026-08-13 · source ↗
Vibecode Day One
Kinda. The core of Day One is buildable in a weekend with the prompt on this page, but there are real gaps: conflict-safe multi-device sync, mature importers and exporters. Read the honest list above before committing.
How much does Day One cost?
Day One costs about $4.17/month (Silver, checked 2026-07-31), which is $50.04 per year.
What do I lose by replacing Day One?
Honestly: conflict-safe multi-device sync; mature importers and exporters; collaborative editing and sharing; native mobile clients. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to Day One?
Yes: moodiary (A real diary app with media, maps and sync, not a notes app wearing a calendar.) Apple Journal (Day One's Apple-shaped core, free because the hardware already collected the money.) The prompt is for when you want it exactly your way.