Vibecode Novlr
track this build4 phases, 12 steps, beginner friendly0%Novlr's solo core is compact: build a local-first novel writing app with fast capture, full-text search, backlinks, tags, and Markdown export. A competent builder can reach a useful personal version in one sitting, while the paid product mainly wins on polish, sync, importers.
You are building a lean indie version of Novlr. 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 ===== # Novlr · indie build A local-first drafting app whose hard problems are never losing a keystroke, counting words in a way that stays meaningful across editing days, and keeping structure and search in an index that can be thrown away and rebuilt from the files. Estimated effort: **weekend**. 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 | | --- | --- | --- | | Shell | Tauri 2 with React and TypeScript | a desktop app with real filesystem access, because the files are the point | | Truth | One plain text file per scene in a folder the writer picks | the manuscript has to outlive this application by decades | | Index | SQLite holding structure, links and search, rebuildable from the folder | an index is disposable and a manuscript is not | | Saving | Temporary file then atomic move, plus a crash journal | a half-written scene file is the failure this whole design avoids | | Snapshots | Scheduled copies retained by count, restore tested in the suite | the cheapest insurance available and the one people skip | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Builds the interface half of the desktop app. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **Rust toolchain and the Tauri prerequisites** · free - Why: File handling, atomic saves and the index live on the Rust side, and Tauri needs platform libraries before the first build succeeds. - Get it: rustup, then the Tauri prerequisites page for your operating system. - Verify: cargo --version prints a version and a bare Tauri app builds - [ ] **A project folder inside your existing backup** · free, or whatever your backup already costs - Why: The manuscript being plain files is only a safety property if the folder is somewhere that is already being backed up, otherwise it is just a different single point of failure. - Get it: Any folder covered by your backup or sync arrangement, ideally with version history. - Verify: A file written in the folder appears in your backup within its stated interval - [ ] **A full-length text to test against** · free - Why: Search timing, index rebuild time and the word count all behave differently at novel length, and a ten scene test project will pass everything. - Get it: A public domain novel split into scene files, or your own draft. - Verify: The project holds something in the order of a hundred thousand words - [ ] **Your word counting rule** · free - Why: It is stamped onto every goal row and it is the number the writer will judge their year by, so it has to be decided once and left alone. - Get it: Write down the rule, including what happens to hyphenated words and contractions, and put it in the README. - Verify: The rule is in the configuration, in the README and asserted by a test ## 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: - Multi-device sync. - Collaboration and sharing. - Importers for proprietary project formats. - Model-generated writing suggestions. - 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 Novlr, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Novlr 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 a local-first novel drafting app whose manuscript is plain files on your own disk to replace Novlr. 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 The manuscript is years of somebody's life and this application is a convenience on top of it. So the words live in plain text files in a folder the writer chose, one file per scene, and the database is an index over those files that can be deleted at any moment and rebuilt. If the only copy of chapter nine is inside a SQLite blob, you have taken custody of something you are not equipped to keep. Everything else follows from that. Every save is written to a temporary file and moved into place, so a crash leaves the previous version rather than a truncated one. Snapshots are taken on a schedule and before every risky operation, retained by count, and restoring one is exercised in the test suite rather than described in the README. A writing tool that loses a session is not a writing tool that needs a better feature, it is one nobody should use again. ### Stack (fixed, do not substitute) - Shell: Tauri 2 with React and TypeScript · a desktop app with real filesystem access, because the files are the point - Truth: One plain text file per scene in a folder the writer picks · the manuscript has to outlive this application by decades - Index: SQLite holding structure, links and search, rebuildable from the folder · an index is disposable and a manuscript is not - Saving: Temporary file then atomic move, plus a crash journal · a half-written scene file is the failure this whole design avoids - Snapshots: Scheduled copies retained by count, restore tested in the suite · the cheapest insurance available and the one people skip ### Data model (create this before Phase 1) - project(path, title, author, created_at, index_version) · the folder is the project, nothing else is - scenes(id, relative_path, chapter_id, ordinal, title, word_count, checksum, modified_at) · mirrors a file, never replaces it - chapters(id, ordinal, title) and an order manifest written into the folder, so reordering is not renaming files - notes(id, kind, title, body_path) · characters, places and research, files in the same folder tree - links(from_scene_id, to_note_id, occurrences) · derived from the text at index time, which is why backlinks survive a rebuild - sessions(id, started_at, ended_at, words_added, words_removed, net_words) · additions and removals kept apart, because an editing day is not a failure - goals(day, target_words, counted_by, net_words) · counted_by records the counting rule in force, so a rule change never rewrites last month ### Phase 1 · Files on disk, index on the side Build: Pick a project folder, write one file per scene, and keep chapter order in a manifest file beside them. Save by writing a temporary file and moving it into place. Build the index by scanning the folder, and provide a command that deletes the index and rebuilds it. Trap: Reordering chapters by renaming the files means every reorder rewrites the folder, breaks any external backup's history and, on the day a rename fails halfway, leaves two scenes with the same name and one gone. Keep the order in a manifest and let the filenames stay still. Done when: Deleting the index and rebuilding restores every scene, chapter order, note and backlink, exercised in the suite. Killing the process during a save leaves the previous file intact and readable, tested by injecting a crash mid-write. Editing a scene in an external text editor is picked up on the next scan without losing the external change. Reordering chapters changes the manifest and renames nothing. Do not build yet: No editor niceties, word counting, search or export. ### Phase 2 · An editor that never eats a keystroke Build: Autosave on idle and on losing focus rather than on every keystroke, append every change to a crash journal between saves, and recover from the journal on launch. Never write while an input method composition is in progress. Trap: Saving on every keystroke while an input method editor is mid-composition writes the half-composed characters into the file, so a writer using a Japanese, Chinese or Vietnamese input method finds their text corrupted in ways that look like the app is broken, because it is. Commit only on composition end, and debounce the save to idle rather than to keys. Done when: Text typed and never explicitly saved is recovered after the process is killed, tested by killing it mid-paragraph. Composition through an input method produces the same file content as the same text typed directly, tested with a composition sequence. Autosave never blocks input, measured by typing continuously through several save cycles. The journal is truncated once its content is safely in the file, so it cannot grow without bound. Do not build yet: No counting, search, links or export yet. ### Phase 3 · Counting words without lying about them Build: Count words with a rule you write down: word boundaries as the Unicode segmentation annex defines them, so that contractions and hyphenated words are one word each. Track additions and removals separately per session, and store the counting rule with every daily goal row. Trap: Splitting on spaces counts a contraction as two words and a line with no spaces as one, and Unicode Annex 29 is explicit that its default word boundaries need tailoring for scripts that do not use spaces at all, so a count that works for English silently becomes nonsense in translation. Use a segmentation-aware count, write the rule into the goal row, and never recompute yesterday's numbers when the rule changes. Done when: A contraction and a hyphenated word each count as one word, asserted against a fixed test string. A day where more words were deleted than added shows both figures and a negative net, rather than a zero or a failure. Changing the counting rule changes today's figure and leaves every historical goal row untouched, tested by switching rules. The manuscript total agrees with the sum of scene counts to the word. Do not build yet: No search, links or export yet. ### Phase 4 · Finding the scene where the knife appears Build: Index scene text for search, derive links to character and place notes from mentions in the text, and show backlinks on each note. Search results land in the scene at the match. Make the whole index rebuildable so a change to the indexing rules is a rescan. Trap: Storing links as rows the writer has to maintain by hand means the links are right on the day they are made and wrong for the rest of the book, because a renamed character does not update anything. Derive links from the text at index time, so a rename is a find and replace followed by a rescan. Done when: A phrase appearing once in one scene is found and the editor opens at the match. Renaming a character and rescanning updates every backlink without any manual step, tested end to end. Deleting the index and rebuilding produces identical search results and identical backlinks. Search over a full-length manuscript returns in a time that is measured and recorded in the README. Do not build yet: No export or snapshot management yet. ### Phase 5 · Snapshots, export, and walking away Build: Take snapshots on a schedule and before every operation that rewrites files, retained by count with a restore exercised in the suite. Export the whole manuscript as one document with chapter breaks, as a folder of files, and in a format a publishing editor can open. Document how to move the project folder to another machine. Trap: A snapshot feature nobody has ever restored from is a folder of files with an unknown format and an untested code path, and the first restore always happens on the worst day. Restore in the test suite, on every run, from a snapshot the suite just took. Done when: A snapshot is restored in the test suite on every run and the restored project matches the original scene for scene. Export produces a single document with chapter breaks whose word count matches the manuscript total. Moving the project folder to another machine and opening it there rebuilds the index and loses nothing, exercised manually once and documented. Snapshot retention is by count and is stated in the README. Do not build yet: Nothing after this. ### Out of scope (and why) - Multi-device sync, which is a conflict resolution problem that has eaten larger projects than this and would put the manuscript at risk to solve. - Collaborative editing and sharing, which needs a server and turns a private drafting tool into a service. - Mature importers from other writing applications, since each proprietary project format is its own reverse engineering exercise. - Writing suggestions from a model, which is a separate product decision and not one to make by accident in a first release. ### README must contain - That the manuscript is plain files in a folder you choose and the database is a disposable index - That every save is atomic and a crash leaves the previous version rather than a truncated one - How snapshots work, how many are kept, and that a restore runs in the test suite on every run - The word counting rule in use, and that changing it never rewrites historical goal rows - How to move the project folder to another machine and what happens on first open there ===== AGENTS.md ===== # Agent instructions · Novlr indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Tauri 2 with React and TypeScript, One plain text file per scene in a folder the writer picks, SQLite holding structure, links and search, rebuildable from the folder, Temporary file then atomic move, plus a crash journal, Scheduled copies retained by count, restore tested in the suite. 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 - Reordering chapters by renaming the files means every reorder rewrites the folder, breaks any external backup's history and, on the day a rename fails halfway, leaves two scenes with the same name and one gone. Keep the order in a manifest and let the filenames stay still. - Saving on every keystroke while an input method editor is mid-composition writes the half-composed characters into the file, so a writer using a Japanese, Chinese or Vietnamese input method finds their text corrupted in ways that look like the app is broken, because it is. Commit only on composition end, and debounce the save to idle rather than to keys. - Splitting on spaces counts a contraction as two words and a line with no spaces as one, and Unicode Annex 29 is explicit that its default word boundaries need tailoring for scripts that do not use spaces at all, so a count that works for English silently becomes nonsense in translation. Use a segmentation-aware count, write the rule into the goal row, and never recompute yesterday's numbers when the rule changes. - Storing links as rows the writer has to maintain by hand means the links are right on the day they are made and wrong for the rest of the book, because a renamed character does not update anything. Derive links from the text at index time, so a rename is a find and replace followed by a rescan. ===== BUILD_PLAN.md ===== # Build plan · Novlr A local-first drafting app whose hard problems are never losing a keystroke, counting words in a way that stays meaningful across editing days, and keeping structure and search in an index that can be thrown away and rebuilt from the files. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Files on disk, index on the side Scenes are plain files, the database is rebuildable, and a crash never truncates one. ### Steps 1. Write through a temporary file and move The single change that separates a safe writing tool from a risky one. Files: `src-tauri/src/save.rs`, `src-tauri/tests/atomic_save.rs` 2. Keep order in a manifest Filenames stay put, so backups and version control stay legible. Files: `src-tauri/src/manifest.rs` 3. Make the index rebuildable and prove it The test that says out loud which artefact is the manuscript. Files: `src-tauri/src/index.rs`, `src-tauri/tests/rebuild.rs` ### Done when - [ ] An index rebuild restores everything from the folder - [ ] A crash mid-save leaves the previous file intact - [ ] External edits are picked up without loss - [ ] Reordering renames no files ### Watch out - Reordering chapters by renaming the files means every reorder rewrites the folder, breaks any external backup's history and, on the day a rename fails halfway, leaves two scenes with the same name and one gone. Keep the order in a manifest and let the filenames stay still. ## Phase 2 · An editor that never eats a keystroke Typing is never interrupted by a save, and nothing is lost between saves. ### Steps 1. Debounce saves to idle and blur Saving per keystroke is how an editor starts stuttering at fifty thousand words. Files: `src/editor/autosave.ts` 2. Journal changes between saves The journal is what turns a crash into a shrug. Files: `src-tauri/src/journal.rs`, `src-tauri/tests/recover.rs` 3. Never write mid-composition Composition end is the only safe commit point for several writing systems. Files: `src/editor/composition.ts` ### Done when - [ ] Unsaved text is recovered after a kill - [ ] Composed text matches directly typed text - [ ] Typing is never blocked by a save - [ ] The journal is truncated after a successful save ### Watch out - Saving on every keystroke while an input method editor is mid-composition writes the half-composed characters into the file, so a writer using a Japanese, Chinese or Vietnamese input method finds their text corrupted in ways that look like the app is broken, because it is. Commit only on composition end, and debounce the save to idle rather than to keys. ## Phase 3 · Counting words without lying about them The count is defined once, recorded with the rule used, and an editing day is not a defeat. ### Steps 1. Count by word boundaries, not by spaces The annex exists because this is harder than it looks. Files: `src/count/words.ts`, `src/count/words.test.ts` 2. Keep additions and removals apart A day spent cutting is work, and a tool that scores it as zero teaches the wrong habit. Files: `src/sessions.ts` 3. Stamp the rule onto every goal row History that silently recomputes itself is history you cannot trust. Files: `src/goals.ts` ### Done when - [ ] Contractions and hyphenated words count as one - [ ] Deleting days show additions, removals and a net - [ ] A rule change never rewrites historical rows - [ ] Scene counts sum to the manuscript total ### Watch out - Splitting on spaces counts a contraction as two words and a line with no spaces as one, and Unicode Annex 29 is explicit that its default word boundaries need tailoring for scripts that do not use spaces at all, so a count that works for English silently becomes nonsense in translation. Use a segmentation-aware count, write the rule into the goal row, and never recompute yesterday's numbers when the rule changes. ## Phase 4 · Finding the scene where the knife appears Search and backlinks work across the whole project and rebuild from the files. ### Steps 1. Derive links from the text Hand-maintained links rot by chapter four. Files: `src-tauri/src/links.rs` 2. Land results in the scene, at the match A result that opens a file and leaves you scrolling is half a feature. Files: `src/search/Results.tsx` 3. Rebuild the index to change the rules A rescan instead of a migration, every time. Files: `src-tauri/src/index.rs` ### Done when - [ ] A one-off phrase is found and opened at the match - [ ] Renaming and rescanning updates every backlink - [ ] A rebuilt index reproduces results exactly - [ ] Search time on a full manuscript is measured ### Watch out - Storing links as rows the writer has to maintain by hand means the links are right on the day they are made and wrong for the rest of the book, because a renamed character does not update anything. Derive links from the text at index time, so a rename is a find and replace followed by a rescan. ## Not in this build - Multi-device sync. - Collaboration and sharing. - Importers for proprietary project formats. - Model-generated writing suggestions. ## After v1, if you want it - A revision mode that compares two snapshots scene by scene, reusing what is already stored - A corkboard view over the manifest, which adds a view and no new source of truth - A compile step with configurable chapter headings, once the export path has been used in anger ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. The folder holding the scene files and the manifest. This is the manuscript. PROJECT_PATH=~/Documents/novel # Required. Disposable. Deleting it and rebuilding is a supported operation. INDEX_PATH=~/.local/share/novel/index.db # Required. Inside the project folder, so a backup of the folder covers them too. SNAPSHOT_DIR=~/Documents/novel/.snapshots # Required. Retention by count. Text compresses well, so keep more than feels necessary. SNAPSHOT_KEEP=50 # Required. Plus one before every operation that rewrites files. SNAPSHOT_INTERVAL_MINUTES=15 # Required. Idle, not per keystroke, and never during an input method composition. AUTOSAVE_IDLE_MS=1200 # Required. Stamped onto every goal row. Changing it never rewrites history. WORD_COUNT_RULE=uax29
You are building a lean indie version of Novlr. 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 ===== # Novlr · indie build A local-first drafting app whose hard problems are never losing a keystroke, counting words in a way that stays meaningful across editing days, and keeping structure and search in an index that can be thrown away and rebuilt from the files. Estimated effort: **weekend**. 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 | | --- | --- | --- | | Shell | Tauri 2 with React and TypeScript | a desktop app with real filesystem access, because the files are the point | | Truth | One plain text file per scene in a folder the writer picks | the manuscript has to outlive this application by decades | | Index | SQLite holding structure, links and search, rebuildable from the folder | an index is disposable and a manuscript is not | | Saving | Temporary file then atomic move, plus a crash journal | a half-written scene file is the failure this whole design avoids | | Snapshots | Scheduled copies retained by count, restore tested in the suite | the cheapest insurance available and the one people skip | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Builds the interface half of the desktop app. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **Rust toolchain and the Tauri prerequisites** · free - Why: File handling, atomic saves and the index live on the Rust side, and Tauri needs platform libraries before the first build succeeds. - Get it: rustup, then the Tauri prerequisites page for your operating system. - Verify: cargo --version prints a version and a bare Tauri app builds - [ ] **A project folder inside your existing backup** · free, or whatever your backup already costs - Why: The manuscript being plain files is only a safety property if the folder is somewhere that is already being backed up, otherwise it is just a different single point of failure. - Get it: Any folder covered by your backup or sync arrangement, ideally with version history. - Verify: A file written in the folder appears in your backup within its stated interval - [ ] **A full-length text to test against** · free - Why: Search timing, index rebuild time and the word count all behave differently at novel length, and a ten scene test project will pass everything. - Get it: A public domain novel split into scene files, or your own draft. - Verify: The project holds something in the order of a hundred thousand words - [ ] **Your word counting rule** · free - Why: It is stamped onto every goal row and it is the number the writer will judge their year by, so it has to be decided once and left alone. - Get it: Write down the rule, including what happens to hyphenated words and contractions, and put it in the README. - Verify: The rule is in the configuration, in the README and asserted by a test ## 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: - Multi-device sync. - Collaboration and sharing. - Importers for proprietary project formats. - Model-generated writing suggestions. - 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 Novlr, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Novlr 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 a local-first novel drafting app whose manuscript is plain files on your own disk to replace Novlr. 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 The manuscript is years of somebody's life and this application is a convenience on top of it. So the words live in plain text files in a folder the writer chose, one file per scene, and the database is an index over those files that can be deleted at any moment and rebuilt. If the only copy of chapter nine is inside a SQLite blob, you have taken custody of something you are not equipped to keep. Everything else follows from that. Every save is written to a temporary file and moved into place, so a crash leaves the previous version rather than a truncated one. Snapshots are taken on a schedule and before every risky operation, retained by count, and restoring one is exercised in the test suite rather than described in the README. A writing tool that loses a session is not a writing tool that needs a better feature, it is one nobody should use again. ### Stack (fixed, do not substitute) - Shell: Tauri 2 with React and TypeScript · a desktop app with real filesystem access, because the files are the point - Truth: One plain text file per scene in a folder the writer picks · the manuscript has to outlive this application by decades - Index: SQLite holding structure, links and search, rebuildable from the folder · an index is disposable and a manuscript is not - Saving: Temporary file then atomic move, plus a crash journal · a half-written scene file is the failure this whole design avoids - Snapshots: Scheduled copies retained by count, restore tested in the suite · the cheapest insurance available and the one people skip ### Data model (create this before Phase 1) - project(path, title, author, created_at, index_version) · the folder is the project, nothing else is - scenes(id, relative_path, chapter_id, ordinal, title, word_count, checksum, modified_at) · mirrors a file, never replaces it - chapters(id, ordinal, title) and an order manifest written into the folder, so reordering is not renaming files - notes(id, kind, title, body_path) · characters, places and research, files in the same folder tree - links(from_scene_id, to_note_id, occurrences) · derived from the text at index time, which is why backlinks survive a rebuild - sessions(id, started_at, ended_at, words_added, words_removed, net_words) · additions and removals kept apart, because an editing day is not a failure - goals(day, target_words, counted_by, net_words) · counted_by records the counting rule in force, so a rule change never rewrites last month ### Phase 1 · Files on disk, index on the side Build: Pick a project folder, write one file per scene, and keep chapter order in a manifest file beside them. Save by writing a temporary file and moving it into place. Build the index by scanning the folder, and provide a command that deletes the index and rebuilds it. Trap: Reordering chapters by renaming the files means every reorder rewrites the folder, breaks any external backup's history and, on the day a rename fails halfway, leaves two scenes with the same name and one gone. Keep the order in a manifest and let the filenames stay still. Done when: Deleting the index and rebuilding restores every scene, chapter order, note and backlink, exercised in the suite. Killing the process during a save leaves the previous file intact and readable, tested by injecting a crash mid-write. Editing a scene in an external text editor is picked up on the next scan without losing the external change. Reordering chapters changes the manifest and renames nothing. Do not build yet: No editor niceties, word counting, search or export. ### Phase 2 · An editor that never eats a keystroke Build: Autosave on idle and on losing focus rather than on every keystroke, append every change to a crash journal between saves, and recover from the journal on launch. Never write while an input method composition is in progress. Trap: Saving on every keystroke while an input method editor is mid-composition writes the half-composed characters into the file, so a writer using a Japanese, Chinese or Vietnamese input method finds their text corrupted in ways that look like the app is broken, because it is. Commit only on composition end, and debounce the save to idle rather than to keys. Done when: Text typed and never explicitly saved is recovered after the process is killed, tested by killing it mid-paragraph. Composition through an input method produces the same file content as the same text typed directly, tested with a composition sequence. Autosave never blocks input, measured by typing continuously through several save cycles. The journal is truncated once its content is safely in the file, so it cannot grow without bound. Do not build yet: No counting, search, links or export yet. ### Phase 3 · Counting words without lying about them Build: Count words with a rule you write down: word boundaries as the Unicode segmentation annex defines them, so that contractions and hyphenated words are one word each. Track additions and removals separately per session, and store the counting rule with every daily goal row. Trap: Splitting on spaces counts a contraction as two words and a line with no spaces as one, and Unicode Annex 29 is explicit that its default word boundaries need tailoring for scripts that do not use spaces at all, so a count that works for English silently becomes nonsense in translation. Use a segmentation-aware count, write the rule into the goal row, and never recompute yesterday's numbers when the rule changes. Done when: A contraction and a hyphenated word each count as one word, asserted against a fixed test string. A day where more words were deleted than added shows both figures and a negative net, rather than a zero or a failure. Changing the counting rule changes today's figure and leaves every historical goal row untouched, tested by switching rules. The manuscript total agrees with the sum of scene counts to the word. Do not build yet: No search, links or export yet. ### Phase 4 · Finding the scene where the knife appears Build: Index scene text for search, derive links to character and place notes from mentions in the text, and show backlinks on each note. Search results land in the scene at the match. Make the whole index rebuildable so a change to the indexing rules is a rescan. Trap: Storing links as rows the writer has to maintain by hand means the links are right on the day they are made and wrong for the rest of the book, because a renamed character does not update anything. Derive links from the text at index time, so a rename is a find and replace followed by a rescan. Done when: A phrase appearing once in one scene is found and the editor opens at the match. Renaming a character and rescanning updates every backlink without any manual step, tested end to end. Deleting the index and rebuilding produces identical search results and identical backlinks. Search over a full-length manuscript returns in a time that is measured and recorded in the README. Do not build yet: No export or snapshot management yet. ### Phase 5 · Snapshots, export, and walking away Build: Take snapshots on a schedule and before every operation that rewrites files, retained by count with a restore exercised in the suite. Export the whole manuscript as one document with chapter breaks, as a folder of files, and in a format a publishing editor can open. Document how to move the project folder to another machine. Trap: A snapshot feature nobody has ever restored from is a folder of files with an unknown format and an untested code path, and the first restore always happens on the worst day. Restore in the test suite, on every run, from a snapshot the suite just took. Done when: A snapshot is restored in the test suite on every run and the restored project matches the original scene for scene. Export produces a single document with chapter breaks whose word count matches the manuscript total. Moving the project folder to another machine and opening it there rebuilds the index and loses nothing, exercised manually once and documented. Snapshot retention is by count and is stated in the README. Do not build yet: Nothing after this. ### Out of scope (and why) - Multi-device sync, which is a conflict resolution problem that has eaten larger projects than this and would put the manuscript at risk to solve. - Collaborative editing and sharing, which needs a server and turns a private drafting tool into a service. - Mature importers from other writing applications, since each proprietary project format is its own reverse engineering exercise. - Writing suggestions from a model, which is a separate product decision and not one to make by accident in a first release. ### README must contain - That the manuscript is plain files in a folder you choose and the database is a disposable index - That every save is atomic and a crash leaves the previous version rather than a truncated one - How snapshots work, how many are kept, and that a restore runs in the test suite on every run - The word counting rule in use, and that changing it never rewrites historical goal rows - How to move the project folder to another machine and what happens on first open there ===== AGENTS.md ===== # Agent instructions · Novlr indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Tauri 2 with React and TypeScript, One plain text file per scene in a folder the writer picks, SQLite holding structure, links and search, rebuildable from the folder, Temporary file then atomic move, plus a crash journal, Scheduled copies retained by count, restore tested in the suite. 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 - Reordering chapters by renaming the files means every reorder rewrites the folder, breaks any external backup's history and, on the day a rename fails halfway, leaves two scenes with the same name and one gone. Keep the order in a manifest and let the filenames stay still. - Saving on every keystroke while an input method editor is mid-composition writes the half-composed characters into the file, so a writer using a Japanese, Chinese or Vietnamese input method finds their text corrupted in ways that look like the app is broken, because it is. Commit only on composition end, and debounce the save to idle rather than to keys. - Splitting on spaces counts a contraction as two words and a line with no spaces as one, and Unicode Annex 29 is explicit that its default word boundaries need tailoring for scripts that do not use spaces at all, so a count that works for English silently becomes nonsense in translation. Use a segmentation-aware count, write the rule into the goal row, and never recompute yesterday's numbers when the rule changes. - Storing links as rows the writer has to maintain by hand means the links are right on the day they are made and wrong for the rest of the book, because a renamed character does not update anything. Derive links from the text at index time, so a rename is a find and replace followed by a rescan. ===== BUILD_PLAN.md ===== # Build plan · Novlr A local-first drafting app whose hard problems are never losing a keystroke, counting words in a way that stays meaningful across editing days, and keeping structure and search in an index that can be thrown away and rebuilt from the files. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Files on disk, index on the side Scenes are plain files, the database is rebuildable, and a crash never truncates one. ### Steps 1. Write through a temporary file and move The single change that separates a safe writing tool from a risky one. Files: `src-tauri/src/save.rs`, `src-tauri/tests/atomic_save.rs` 2. Keep order in a manifest Filenames stay put, so backups and version control stay legible. Files: `src-tauri/src/manifest.rs` 3. Make the index rebuildable and prove it The test that says out loud which artefact is the manuscript. Files: `src-tauri/src/index.rs`, `src-tauri/tests/rebuild.rs` ### Done when - [ ] An index rebuild restores everything from the folder - [ ] A crash mid-save leaves the previous file intact - [ ] External edits are picked up without loss - [ ] Reordering renames no files ### Watch out - Reordering chapters by renaming the files means every reorder rewrites the folder, breaks any external backup's history and, on the day a rename fails halfway, leaves two scenes with the same name and one gone. Keep the order in a manifest and let the filenames stay still. ## Phase 2 · An editor that never eats a keystroke Typing is never interrupted by a save, and nothing is lost between saves. ### Steps 1. Debounce saves to idle and blur Saving per keystroke is how an editor starts stuttering at fifty thousand words. Files: `src/editor/autosave.ts` 2. Journal changes between saves The journal is what turns a crash into a shrug. Files: `src-tauri/src/journal.rs`, `src-tauri/tests/recover.rs` 3. Never write mid-composition Composition end is the only safe commit point for several writing systems. Files: `src/editor/composition.ts` ### Done when - [ ] Unsaved text is recovered after a kill - [ ] Composed text matches directly typed text - [ ] Typing is never blocked by a save - [ ] The journal is truncated after a successful save ### Watch out - Saving on every keystroke while an input method editor is mid-composition writes the half-composed characters into the file, so a writer using a Japanese, Chinese or Vietnamese input method finds their text corrupted in ways that look like the app is broken, because it is. Commit only on composition end, and debounce the save to idle rather than to keys. ## Phase 3 · Counting words without lying about them The count is defined once, recorded with the rule used, and an editing day is not a defeat. ### Steps 1. Count by word boundaries, not by spaces The annex exists because this is harder than it looks. Files: `src/count/words.ts`, `src/count/words.test.ts` 2. Keep additions and removals apart A day spent cutting is work, and a tool that scores it as zero teaches the wrong habit. Files: `src/sessions.ts` 3. Stamp the rule onto every goal row History that silently recomputes itself is history you cannot trust. Files: `src/goals.ts` ### Done when - [ ] Contractions and hyphenated words count as one - [ ] Deleting days show additions, removals and a net - [ ] A rule change never rewrites historical rows - [ ] Scene counts sum to the manuscript total ### Watch out - Splitting on spaces counts a contraction as two words and a line with no spaces as one, and Unicode Annex 29 is explicit that its default word boundaries need tailoring for scripts that do not use spaces at all, so a count that works for English silently becomes nonsense in translation. Use a segmentation-aware count, write the rule into the goal row, and never recompute yesterday's numbers when the rule changes. ## Phase 4 · Finding the scene where the knife appears Search and backlinks work across the whole project and rebuild from the files. ### Steps 1. Derive links from the text Hand-maintained links rot by chapter four. Files: `src-tauri/src/links.rs` 2. Land results in the scene, at the match A result that opens a file and leaves you scrolling is half a feature. Files: `src/search/Results.tsx` 3. Rebuild the index to change the rules A rescan instead of a migration, every time. Files: `src-tauri/src/index.rs` ### Done when - [ ] A one-off phrase is found and opened at the match - [ ] Renaming and rescanning updates every backlink - [ ] A rebuilt index reproduces results exactly - [ ] Search time on a full manuscript is measured ### Watch out - Storing links as rows the writer has to maintain by hand means the links are right on the day they are made and wrong for the rest of the book, because a renamed character does not update anything. Derive links from the text at index time, so a rename is a find and replace followed by a rescan. ## Not in this build - Multi-device sync. - Collaboration and sharing. - Importers for proprietary project formats. - Model-generated writing suggestions. ## After v1, if you want it - A revision mode that compares two snapshots scene by scene, reusing what is already stored - A corkboard view over the manifest, which adds a view and no new source of truth - A compile step with configurable chapter headings, once the export path has been used in anger ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. The folder holding the scene files and the manifest. This is the manuscript. PROJECT_PATH=~/Documents/novel # Required. Disposable. Deleting it and rebuilding is a supported operation. INDEX_PATH=~/.local/share/novel/index.db # Required. Inside the project folder, so a backup of the folder covers them too. SNAPSHOT_DIR=~/Documents/novel/.snapshots # Required. Retention by count. Text compresses well, so keep more than feels necessary. SNAPSHOT_KEEP=50 # Required. Plus one before every operation that rewrites files. SNAPSHOT_INTERVAL_MINUTES=15 # Required. Idle, not per keystroke, and never during an input method composition. AUTOSAVE_IDLE_MS=1200 # Required. Stamped onto every goal row. Changing it never rewrites history. WORD_COUNT_RULE=uax29
You are building a production product version of Novlr. 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 ===== # Novlr · product brief ## Problem Novlr's solo core is compact: build a local-first novel writing app with fast capture, full-text search, backlinks, tags, and Markdown export. A competent builder can reach a useful personal version in one sitting, while the paid product mainly wins on polish, sync, importers. ## Product outcome A drafting app where the manuscript is a folder you could open in any text editor, a crash costs nothing, the daily count means the same thing in month nine as in week one, and a restore has been tested every time the suite ran. ## 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 - Multi-device sync. - Collaboration and sharing. - Importers for proprietary project formats. - Model-generated writing suggestions. - conflict-safe multi-device sync - mature importers and exporters - collaborative editing and sharing - native mobile clients ## Success criteria - An index rebuild is confirmed to restore everything from the folder - A crash mid-save is confirmed to leave the previous file intact - Unsaved text is confirmed recoverable from the journal - A snapshot restore is confirmed to run in the suite - A counting rule change is confirmed not to rewrite historical rows ===== BRIEF.md ===== # Build brief · Novlr 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 a local-first novel drafting app whose manuscript is plain files on your own disk to replace Novlr. 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 The manuscript is years of somebody's life and this application is a convenience on top of it. So the words live in plain text files in a folder the writer chose, one file per scene, and the database is an index over those files that can be deleted at any moment and rebuilt. If the only copy of chapter nine is inside a SQLite blob, you have taken custody of something you are not equipped to keep. Everything else follows from that. Every save is written to a temporary file and moved into place, so a crash leaves the previous version rather than a truncated one. Snapshots are taken on a schedule and before every risky operation, retained by count, and restoring one is exercised in the test suite rather than described in the README. A writing tool that loses a session is not a writing tool that needs a better feature, it is one nobody should use again. ### Stack (fixed, do not substitute) - Shell: Tauri 2 with React and TypeScript · a desktop app with real filesystem access, because the files are the point - Truth: One plain text file per scene in a folder the writer picks · the manuscript has to outlive this application by decades - Index: SQLite holding structure, links and search, rebuildable from the folder · an index is disposable and a manuscript is not - Saving: Temporary file then atomic move, plus a crash journal · a half-written scene file is the failure this whole design avoids - Snapshots: Scheduled copies retained by count, restore tested in the suite · the cheapest insurance available and the one people skip ### Data model (create this before Phase 1) - project(path, title, author, created_at, index_version) · the folder is the project, nothing else is - scenes(id, relative_path, chapter_id, ordinal, title, word_count, checksum, modified_at) · mirrors a file, never replaces it - chapters(id, ordinal, title) and an order manifest written into the folder, so reordering is not renaming files - notes(id, kind, title, body_path) · characters, places and research, files in the same folder tree - links(from_scene_id, to_note_id, occurrences) · derived from the text at index time, which is why backlinks survive a rebuild - sessions(id, started_at, ended_at, words_added, words_removed, net_words) · additions and removals kept apart, because an editing day is not a failure - goals(day, target_words, counted_by, net_words) · counted_by records the counting rule in force, so a rule change never rewrites last month ### Phase 1 · Files on disk, index on the side Build: Pick a project folder, write one file per scene, and keep chapter order in a manifest file beside them. Save by writing a temporary file and moving it into place. Build the index by scanning the folder, and provide a command that deletes the index and rebuilds it. Trap: Reordering chapters by renaming the files means every reorder rewrites the folder, breaks any external backup's history and, on the day a rename fails halfway, leaves two scenes with the same name and one gone. Keep the order in a manifest and let the filenames stay still. Done when: Deleting the index and rebuilding restores every scene, chapter order, note and backlink, exercised in the suite. Killing the process during a save leaves the previous file intact and readable, tested by injecting a crash mid-write. Editing a scene in an external text editor is picked up on the next scan without losing the external change. Reordering chapters changes the manifest and renames nothing. Do not build yet: No editor niceties, word counting, search or export. ### Phase 2 · An editor that never eats a keystroke Build: Autosave on idle and on losing focus rather than on every keystroke, append every change to a crash journal between saves, and recover from the journal on launch. Never write while an input method composition is in progress. Trap: Saving on every keystroke while an input method editor is mid-composition writes the half-composed characters into the file, so a writer using a Japanese, Chinese or Vietnamese input method finds their text corrupted in ways that look like the app is broken, because it is. Commit only on composition end, and debounce the save to idle rather than to keys. Done when: Text typed and never explicitly saved is recovered after the process is killed, tested by killing it mid-paragraph. Composition through an input method produces the same file content as the same text typed directly, tested with a composition sequence. Autosave never blocks input, measured by typing continuously through several save cycles. The journal is truncated once its content is safely in the file, so it cannot grow without bound. Do not build yet: No counting, search, links or export yet. ### Phase 3 · Counting words without lying about them Build: Count words with a rule you write down: word boundaries as the Unicode segmentation annex defines them, so that contractions and hyphenated words are one word each. Track additions and removals separately per session, and store the counting rule with every daily goal row. Trap: Splitting on spaces counts a contraction as two words and a line with no spaces as one, and Unicode Annex 29 is explicit that its default word boundaries need tailoring for scripts that do not use spaces at all, so a count that works for English silently becomes nonsense in translation. Use a segmentation-aware count, write the rule into the goal row, and never recompute yesterday's numbers when the rule changes. Done when: A contraction and a hyphenated word each count as one word, asserted against a fixed test string. A day where more words were deleted than added shows both figures and a negative net, rather than a zero or a failure. Changing the counting rule changes today's figure and leaves every historical goal row untouched, tested by switching rules. The manuscript total agrees with the sum of scene counts to the word. Do not build yet: No search, links or export yet. ### Phase 4 · Finding the scene where the knife appears Build: Index scene text for search, derive links to character and place notes from mentions in the text, and show backlinks on each note. Search results land in the scene at the match. Make the whole index rebuildable so a change to the indexing rules is a rescan. Trap: Storing links as rows the writer has to maintain by hand means the links are right on the day they are made and wrong for the rest of the book, because a renamed character does not update anything. Derive links from the text at index time, so a rename is a find and replace followed by a rescan. Done when: A phrase appearing once in one scene is found and the editor opens at the match. Renaming a character and rescanning updates every backlink without any manual step, tested end to end. Deleting the index and rebuilding produces identical search results and identical backlinks. Search over a full-length manuscript returns in a time that is measured and recorded in the README. Do not build yet: No export or snapshot management yet. ### Phase 5 · Snapshots, export, and walking away Build: Take snapshots on a schedule and before every operation that rewrites files, retained by count with a restore exercised in the suite. Export the whole manuscript as one document with chapter breaks, as a folder of files, and in a format a publishing editor can open. Document how to move the project folder to another machine. Trap: A snapshot feature nobody has ever restored from is a folder of files with an unknown format and an untested code path, and the first restore always happens on the worst day. Restore in the test suite, on every run, from a snapshot the suite just took. Done when: A snapshot is restored in the test suite on every run and the restored project matches the original scene for scene. Export produces a single document with chapter breaks whose word count matches the manuscript total. Moving the project folder to another machine and opening it there rebuilds the index and loses nothing, exercised manually once and documented. Snapshot retention is by count and is stated in the README. Do not build yet: Nothing after this. ### Out of scope (and why) - Multi-device sync, which is a conflict resolution problem that has eaten larger projects than this and would put the manuscript at risk to solve. - Collaborative editing and sharing, which needs a server and turns a private drafting tool into a service. - Mature importers from other writing applications, since each proprietary project format is its own reverse engineering exercise. - Writing suggestions from a model, which is a separate product decision and not one to make by accident in a first release. ### README must contain - That the manuscript is plain files in a folder you choose and the database is a disposable index - That every save is atomic and a crash leaves the previous version rather than a truncated one - How snapshots work, how many are kept, and that a restore runs in the test suite on every run - The word counting rule in use, and that changing it never rewrites historical goal rows - How to move the project folder to another machine and what happens on first open there ===== ARCHITECTURE.md ===== # Architecture · Novlr ## Stack | Part | Choice | Why | | --- | --- | --- | | Shell | Tauri 2 with React and TypeScript | a desktop app with real filesystem access, because the files are the point | | Truth | One plain text file per scene in a folder the writer picks | the manuscript has to outlive this application by decades | | Index | SQLite holding structure, links and search, rebuildable from the folder | an index is disposable and a manuscript is not | | Saving | Temporary file then atomic move, plus a crash journal | a half-written scene file is the failure this whole design avoids | | Snapshots | Scheduled copies retained by count, restore tested in the suite | the cheapest insurance available and the one people skip | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Files | Scene files, the manifest and atomic saves. | Not swappable for storing text in the database, which is the one decision this build refuses. | | Index | Structure, links and search, rebuilt by scanning. | Any index, provided it stays disposable. | | Editor | Autosave timing, journalling and composition safety. | Any editor component, provided composition end is the commit point. | | Counting | Segmentation-aware counts and per-rule history. | Any rule, provided it is stamped onto rows rather than applied retroactively. | | Snapshots | Scheduled copies and a tested restore. | Any mechanism, provided the restore runs in the suite. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `PROJECT_PATH` · required · The folder holding the scene files and the manifest. This is the manuscript. - `INDEX_PATH` · required · Disposable. Deleting it and rebuilding is a supported operation. - `SNAPSHOT_DIR` · required · Inside the project folder, so a backup of the folder covers them too. - `SNAPSHOT_KEEP` · required · Retention by count. Text compresses well, so keep more than feels necessary. - `SNAPSHOT_INTERVAL_MINUTES` · required · Plus one before every operation that rewrites files. - `AUTOSAVE_IDLE_MS` · required · Idle, not per keystroke, and never during an input method composition. - `WORD_COUNT_RULE` · required · Stamped onto every goal row. Changing it never rewrites history. ## 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 · Novlr product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Tauri 2 with React and TypeScript, One plain text file per scene in a folder the writer picks, SQLite holding structure, links and search, rebuildable from the folder, Temporary file then atomic move, plus a crash journal, Scheduled copies retained by count, restore tested in the suite. - 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 - Reordering chapters by renaming the files means every reorder rewrites the folder, breaks any external backup's history and, on the day a rename fails halfway, leaves two scenes with the same name and one gone. Keep the order in a manifest and let the filenames stay still. - Saving on every keystroke while an input method editor is mid-composition writes the half-composed characters into the file, so a writer using a Japanese, Chinese or Vietnamese input method finds their text corrupted in ways that look like the app is broken, because it is. Commit only on composition end, and debounce the save to idle rather than to keys. - Splitting on spaces counts a contraction as two words and a line with no spaces as one, and Unicode Annex 29 is explicit that its default word boundaries need tailoring for scripts that do not use spaces at all, so a count that works for English silently becomes nonsense in translation. Use a segmentation-aware count, write the rule into the goal row, and never recompute yesterday's numbers when the rule changes. - Storing links as rows the writer has to maintain by hand means the links are right on the day they are made and wrong for the rest of the book, because a renamed character does not update anything. Derive links from the text at index time, so a rename is a find and replace followed by a rescan. - A snapshot feature nobody has ever restored from is a folder of files with an unknown format and an untested code path, and the first restore always happens on the worst day. Restore in the test suite, on every run, from a snapshot the suite just took. ===== MILESTONES.md ===== # Delivery milestones · Novlr Estimated effort: **weekend** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Files on disk, index on the side Scenes are plain files, the database is rebuildable, and a crash never truncates one. ### Steps 1. Write through a temporary file and move The single change that separates a safe writing tool from a risky one. Files: `src-tauri/src/save.rs`, `src-tauri/tests/atomic_save.rs` 2. Keep order in a manifest Filenames stay put, so backups and version control stay legible. Files: `src-tauri/src/manifest.rs` 3. Make the index rebuildable and prove it The test that says out loud which artefact is the manuscript. Files: `src-tauri/src/index.rs`, `src-tauri/tests/rebuild.rs` ### Done when - [ ] An index rebuild restores everything from the folder - [ ] A crash mid-save leaves the previous file intact - [ ] External edits are picked up without loss - [ ] Reordering renames no files ### Watch out - Reordering chapters by renaming the files means every reorder rewrites the folder, breaks any external backup's history and, on the day a rename fails halfway, leaves two scenes with the same name and one gone. Keep the order in a manifest and let the filenames stay still. ## M2 · An editor that never eats a keystroke Typing is never interrupted by a save, and nothing is lost between saves. ### Steps 1. Debounce saves to idle and blur Saving per keystroke is how an editor starts stuttering at fifty thousand words. Files: `src/editor/autosave.ts` 2. Journal changes between saves The journal is what turns a crash into a shrug. Files: `src-tauri/src/journal.rs`, `src-tauri/tests/recover.rs` 3. Never write mid-composition Composition end is the only safe commit point for several writing systems. Files: `src/editor/composition.ts` ### Done when - [ ] Unsaved text is recovered after a kill - [ ] Composed text matches directly typed text - [ ] Typing is never blocked by a save - [ ] The journal is truncated after a successful save ### Watch out - Saving on every keystroke while an input method editor is mid-composition writes the half-composed characters into the file, so a writer using a Japanese, Chinese or Vietnamese input method finds their text corrupted in ways that look like the app is broken, because it is. Commit only on composition end, and debounce the save to idle rather than to keys. ## M3 · Counting words without lying about them The count is defined once, recorded with the rule used, and an editing day is not a defeat. ### Steps 1. Count by word boundaries, not by spaces The annex exists because this is harder than it looks. Files: `src/count/words.ts`, `src/count/words.test.ts` 2. Keep additions and removals apart A day spent cutting is work, and a tool that scores it as zero teaches the wrong habit. Files: `src/sessions.ts` 3. Stamp the rule onto every goal row History that silently recomputes itself is history you cannot trust. Files: `src/goals.ts` ### Done when - [ ] Contractions and hyphenated words count as one - [ ] Deleting days show additions, removals and a net - [ ] A rule change never rewrites historical rows - [ ] Scene counts sum to the manuscript total ### Watch out - Splitting on spaces counts a contraction as two words and a line with no spaces as one, and Unicode Annex 29 is explicit that its default word boundaries need tailoring for scripts that do not use spaces at all, so a count that works for English silently becomes nonsense in translation. Use a segmentation-aware count, write the rule into the goal row, and never recompute yesterday's numbers when the rule changes. ## M4 · Finding the scene where the knife appears Search and backlinks work across the whole project and rebuild from the files. ### Steps 1. Derive links from the text Hand-maintained links rot by chapter four. Files: `src-tauri/src/links.rs` 2. Land results in the scene, at the match A result that opens a file and leaves you scrolling is half a feature. Files: `src/search/Results.tsx` 3. Rebuild the index to change the rules A rescan instead of a migration, every time. Files: `src-tauri/src/index.rs` ### Done when - [ ] A one-off phrase is found and opened at the match - [ ] Renaming and rescanning updates every backlink - [ ] A rebuilt index reproduces results exactly - [ ] Search time on a full manuscript is measured ### Watch out - Storing links as rows the writer has to maintain by hand means the links are right on the day they are made and wrong for the rest of the book, because a renamed character does not update anything. Derive links from the text at index time, so a rename is a find and replace followed by a rescan. ## M5 · Snapshots, export, and walking away (production only) A bad day is recoverable and the manuscript leaves in a form an editor will accept. ### Steps 1. Restore from a snapshot in the suite Every run, from a snapshot taken in that run. Otherwise it is a hope. Files: `src-tauri/tests/snapshot_restore.rs` 2. Export one document and a folder One for the editor, one for the writer, both from the same files. Files: `src/export/manuscript.ts` 3. Document moving the folder The project is a folder, and the README should make that obvious enough to be reassuring. Files: `README.md` ### Done when - [ ] A snapshot restore runs in the suite every time - [ ] Export word count matches the manuscript total - [ ] Moving the folder loses nothing - [ ] Snapshot retention is stated and enforced ### Watch out - A snapshot feature nobody has ever restored from is a folder of files with an unknown format and an untested code path, and the first restore always happens on the worst day. Restore in the test suite, on every run, from a snapshot the suite just took. ===== OPERATIONS.md ===== # Operations · Novlr ## Backup The project folder, by whatever already backs up your documents, with the snapshots living inside it so one arrangement covers both. The index is deliberately not worth backing up. ## Restore Restore the folder, open it, let the index rebuild, and check the manuscript total against the last figure you remember. That total is the quickest integrity check available and it takes a second. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring For a local application the checks live in the suite: a snapshot restore on every run, an index rebuild that reproduces search results, and a crash injected mid-save leaving the previous file intact. ## Incident checklist The incident in this category is always the same one, a lost session, and the answers are the journal for minutes, the snapshots for hours and the folder backup for days. If all three fail together the design was wrong, which is why each is tested separately. 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 - [ ] An index rebuild is confirmed to restore everything from the folder - [ ] A crash mid-save is confirmed to leave the previous file intact - [ ] Unsaved text is confirmed recoverable from the journal - [ ] A snapshot restore is confirmed to run in the suite - [ ] A counting rule change is confirmed not to rewrite historical rows ## Launch constraint Do not market omitted Novlr 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. The folder holding the scene files and the manifest. This is the manuscript. PROJECT_PATH=~/Documents/novel # Required. Disposable. Deleting it and rebuilding is a supported operation. INDEX_PATH=~/.local/share/novel/index.db # Required. Inside the project folder, so a backup of the folder covers them too. SNAPSHOT_DIR=~/Documents/novel/.snapshots # Required. Retention by count. Text compresses well, so keep more than feels necessary. SNAPSHOT_KEEP=50 # Required. Plus one before every operation that rewrites files. SNAPSHOT_INTERVAL_MINUTES=15 # Required. Idle, not per keystroke, and never during an input method composition. AUTOSAVE_IDLE_MS=1200 # Required. Stamped onto every goal row. Changing it never rewrites history. WORD_COUNT_RULE=uax29
# Novlr · indie build A local-first drafting app whose hard problems are never losing a keystroke, counting words in a way that stays meaningful across editing days, and keeping structure and search in an index that can be thrown away and rebuilt from the files. Estimated effort: **weekend**. 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 | | --- | --- | --- | | Shell | Tauri 2 with React and TypeScript | a desktop app with real filesystem access, because the files are the point | | Truth | One plain text file per scene in a folder the writer picks | the manuscript has to outlive this application by decades | | Index | SQLite holding structure, links and search, rebuildable from the folder | an index is disposable and a manuscript is not | | Saving | Temporary file then atomic move, plus a crash journal | a half-written scene file is the failure this whole design avoids | | Snapshots | Scheduled copies retained by count, restore tested in the suite | the cheapest insurance available and the one people skip | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Builds the interface half of the desktop app. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **Rust toolchain and the Tauri prerequisites** · free - Why: File handling, atomic saves and the index live on the Rust side, and Tauri needs platform libraries before the first build succeeds. - Get it: rustup, then the Tauri prerequisites page for your operating system. - Verify: cargo --version prints a version and a bare Tauri app builds - [ ] **A project folder inside your existing backup** · free, or whatever your backup already costs - Why: The manuscript being plain files is only a safety property if the folder is somewhere that is already being backed up, otherwise it is just a different single point of failure. - Get it: Any folder covered by your backup or sync arrangement, ideally with version history. - Verify: A file written in the folder appears in your backup within its stated interval - [ ] **A full-length text to test against** · free - Why: Search timing, index rebuild time and the word count all behave differently at novel length, and a ten scene test project will pass everything. - Get it: A public domain novel split into scene files, or your own draft. - Verify: The project holds something in the order of a hundred thousand words - [ ] **Your word counting rule** · free - Why: It is stamped onto every goal row and it is the number the writer will judge their year by, so it has to be decided once and left alone. - Get it: Write down the rule, including what happens to hyphenated words and contractions, and put it in the README. - Verify: The rule is in the configuration, in the README and asserted by a test ## 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: - Multi-device sync. - Collaboration and sharing. - Importers for proprietary project formats. - Model-generated writing suggestions. - 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 Novlr, and the README should say so rather than pretend.
# Build brief · Novlr 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 a local-first novel drafting app whose manuscript is plain files on your own disk to replace Novlr. 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 The manuscript is years of somebody's life and this application is a convenience on top of it. So the words live in plain text files in a folder the writer chose, one file per scene, and the database is an index over those files that can be deleted at any moment and rebuilt. If the only copy of chapter nine is inside a SQLite blob, you have taken custody of something you are not equipped to keep. Everything else follows from that. Every save is written to a temporary file and moved into place, so a crash leaves the previous version rather than a truncated one. Snapshots are taken on a schedule and before every risky operation, retained by count, and restoring one is exercised in the test suite rather than described in the README. A writing tool that loses a session is not a writing tool that needs a better feature, it is one nobody should use again. ### Stack (fixed, do not substitute) - Shell: Tauri 2 with React and TypeScript · a desktop app with real filesystem access, because the files are the point - Truth: One plain text file per scene in a folder the writer picks · the manuscript has to outlive this application by decades - Index: SQLite holding structure, links and search, rebuildable from the folder · an index is disposable and a manuscript is not - Saving: Temporary file then atomic move, plus a crash journal · a half-written scene file is the failure this whole design avoids - Snapshots: Scheduled copies retained by count, restore tested in the suite · the cheapest insurance available and the one people skip ### Data model (create this before Phase 1) - project(path, title, author, created_at, index_version) · the folder is the project, nothing else is - scenes(id, relative_path, chapter_id, ordinal, title, word_count, checksum, modified_at) · mirrors a file, never replaces it - chapters(id, ordinal, title) and an order manifest written into the folder, so reordering is not renaming files - notes(id, kind, title, body_path) · characters, places and research, files in the same folder tree - links(from_scene_id, to_note_id, occurrences) · derived from the text at index time, which is why backlinks survive a rebuild - sessions(id, started_at, ended_at, words_added, words_removed, net_words) · additions and removals kept apart, because an editing day is not a failure - goals(day, target_words, counted_by, net_words) · counted_by records the counting rule in force, so a rule change never rewrites last month ### Phase 1 · Files on disk, index on the side Build: Pick a project folder, write one file per scene, and keep chapter order in a manifest file beside them. Save by writing a temporary file and moving it into place. Build the index by scanning the folder, and provide a command that deletes the index and rebuilds it. Trap: Reordering chapters by renaming the files means every reorder rewrites the folder, breaks any external backup's history and, on the day a rename fails halfway, leaves two scenes with the same name and one gone. Keep the order in a manifest and let the filenames stay still. Done when: Deleting the index and rebuilding restores every scene, chapter order, note and backlink, exercised in the suite. Killing the process during a save leaves the previous file intact and readable, tested by injecting a crash mid-write. Editing a scene in an external text editor is picked up on the next scan without losing the external change. Reordering chapters changes the manifest and renames nothing. Do not build yet: No editor niceties, word counting, search or export. ### Phase 2 · An editor that never eats a keystroke Build: Autosave on idle and on losing focus rather than on every keystroke, append every change to a crash journal between saves, and recover from the journal on launch. Never write while an input method composition is in progress. Trap: Saving on every keystroke while an input method editor is mid-composition writes the half-composed characters into the file, so a writer using a Japanese, Chinese or Vietnamese input method finds their text corrupted in ways that look like the app is broken, because it is. Commit only on composition end, and debounce the save to idle rather than to keys. Done when: Text typed and never explicitly saved is recovered after the process is killed, tested by killing it mid-paragraph. Composition through an input method produces the same file content as the same text typed directly, tested with a composition sequence. Autosave never blocks input, measured by typing continuously through several save cycles. The journal is truncated once its content is safely in the file, so it cannot grow without bound. Do not build yet: No counting, search, links or export yet. ### Phase 3 · Counting words without lying about them Build: Count words with a rule you write down: word boundaries as the Unicode segmentation annex defines them, so that contractions and hyphenated words are one word each. Track additions and removals separately per session, and store the counting rule with every daily goal row. Trap: Splitting on spaces counts a contraction as two words and a line with no spaces as one, and Unicode Annex 29 is explicit that its default word boundaries need tailoring for scripts that do not use spaces at all, so a count that works for English silently becomes nonsense in translation. Use a segmentation-aware count, write the rule into the goal row, and never recompute yesterday's numbers when the rule changes. Done when: A contraction and a hyphenated word each count as one word, asserted against a fixed test string. A day where more words were deleted than added shows both figures and a negative net, rather than a zero or a failure. Changing the counting rule changes today's figure and leaves every historical goal row untouched, tested by switching rules. The manuscript total agrees with the sum of scene counts to the word. Do not build yet: No search, links or export yet. ### Phase 4 · Finding the scene where the knife appears Build: Index scene text for search, derive links to character and place notes from mentions in the text, and show backlinks on each note. Search results land in the scene at the match. Make the whole index rebuildable so a change to the indexing rules is a rescan. Trap: Storing links as rows the writer has to maintain by hand means the links are right on the day they are made and wrong for the rest of the book, because a renamed character does not update anything. Derive links from the text at index time, so a rename is a find and replace followed by a rescan. Done when: A phrase appearing once in one scene is found and the editor opens at the match. Renaming a character and rescanning updates every backlink without any manual step, tested end to end. Deleting the index and rebuilding produces identical search results and identical backlinks. Search over a full-length manuscript returns in a time that is measured and recorded in the README. Do not build yet: No export or snapshot management yet. ### Phase 5 · Snapshots, export, and walking away Build: Take snapshots on a schedule and before every operation that rewrites files, retained by count with a restore exercised in the suite. Export the whole manuscript as one document with chapter breaks, as a folder of files, and in a format a publishing editor can open. Document how to move the project folder to another machine. Trap: A snapshot feature nobody has ever restored from is a folder of files with an unknown format and an untested code path, and the first restore always happens on the worst day. Restore in the test suite, on every run, from a snapshot the suite just took. Done when: A snapshot is restored in the test suite on every run and the restored project matches the original scene for scene. Export produces a single document with chapter breaks whose word count matches the manuscript total. Moving the project folder to another machine and opening it there rebuilds the index and loses nothing, exercised manually once and documented. Snapshot retention is by count and is stated in the README. Do not build yet: Nothing after this. ### Out of scope (and why) - Multi-device sync, which is a conflict resolution problem that has eaten larger projects than this and would put the manuscript at risk to solve. - Collaborative editing and sharing, which needs a server and turns a private drafting tool into a service. - Mature importers from other writing applications, since each proprietary project format is its own reverse engineering exercise. - Writing suggestions from a model, which is a separate product decision and not one to make by accident in a first release. ### README must contain - That the manuscript is plain files in a folder you choose and the database is a disposable index - That every save is atomic and a crash leaves the previous version rather than a truncated one - How snapshots work, how many are kept, and that a restore runs in the test suite on every run - The word counting rule in use, and that changing it never rewrites historical goal rows - How to move the project folder to another machine and what happens on first open there
# Agent instructions · Novlr indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Tauri 2 with React and TypeScript, One plain text file per scene in a folder the writer picks, SQLite holding structure, links and search, rebuildable from the folder, Temporary file then atomic move, plus a crash journal, Scheduled copies retained by count, restore tested in the suite. 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 - Reordering chapters by renaming the files means every reorder rewrites the folder, breaks any external backup's history and, on the day a rename fails halfway, leaves two scenes with the same name and one gone. Keep the order in a manifest and let the filenames stay still. - Saving on every keystroke while an input method editor is mid-composition writes the half-composed characters into the file, so a writer using a Japanese, Chinese or Vietnamese input method finds their text corrupted in ways that look like the app is broken, because it is. Commit only on composition end, and debounce the save to idle rather than to keys. - Splitting on spaces counts a contraction as two words and a line with no spaces as one, and Unicode Annex 29 is explicit that its default word boundaries need tailoring for scripts that do not use spaces at all, so a count that works for English silently becomes nonsense in translation. Use a segmentation-aware count, write the rule into the goal row, and never recompute yesterday's numbers when the rule changes. - Storing links as rows the writer has to maintain by hand means the links are right on the day they are made and wrong for the rest of the book, because a renamed character does not update anything. Derive links from the text at index time, so a rename is a find and replace followed by a rescan.
# Build plan · Novlr A local-first drafting app whose hard problems are never losing a keystroke, counting words in a way that stays meaningful across editing days, and keeping structure and search in an index that can be thrown away and rebuilt from the files. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Files on disk, index on the side Scenes are plain files, the database is rebuildable, and a crash never truncates one. ### Steps 1. Write through a temporary file and move The single change that separates a safe writing tool from a risky one. Files: `src-tauri/src/save.rs`, `src-tauri/tests/atomic_save.rs` 2. Keep order in a manifest Filenames stay put, so backups and version control stay legible. Files: `src-tauri/src/manifest.rs` 3. Make the index rebuildable and prove it The test that says out loud which artefact is the manuscript. Files: `src-tauri/src/index.rs`, `src-tauri/tests/rebuild.rs` ### Done when - [ ] An index rebuild restores everything from the folder - [ ] A crash mid-save leaves the previous file intact - [ ] External edits are picked up without loss - [ ] Reordering renames no files ### Watch out - Reordering chapters by renaming the files means every reorder rewrites the folder, breaks any external backup's history and, on the day a rename fails halfway, leaves two scenes with the same name and one gone. Keep the order in a manifest and let the filenames stay still. ## Phase 2 · An editor that never eats a keystroke Typing is never interrupted by a save, and nothing is lost between saves. ### Steps 1. Debounce saves to idle and blur Saving per keystroke is how an editor starts stuttering at fifty thousand words. Files: `src/editor/autosave.ts` 2. Journal changes between saves The journal is what turns a crash into a shrug. Files: `src-tauri/src/journal.rs`, `src-tauri/tests/recover.rs` 3. Never write mid-composition Composition end is the only safe commit point for several writing systems. Files: `src/editor/composition.ts` ### Done when - [ ] Unsaved text is recovered after a kill - [ ] Composed text matches directly typed text - [ ] Typing is never blocked by a save - [ ] The journal is truncated after a successful save ### Watch out - Saving on every keystroke while an input method editor is mid-composition writes the half-composed characters into the file, so a writer using a Japanese, Chinese or Vietnamese input method finds their text corrupted in ways that look like the app is broken, because it is. Commit only on composition end, and debounce the save to idle rather than to keys. ## Phase 3 · Counting words without lying about them The count is defined once, recorded with the rule used, and an editing day is not a defeat. ### Steps 1. Count by word boundaries, not by spaces The annex exists because this is harder than it looks. Files: `src/count/words.ts`, `src/count/words.test.ts` 2. Keep additions and removals apart A day spent cutting is work, and a tool that scores it as zero teaches the wrong habit. Files: `src/sessions.ts` 3. Stamp the rule onto every goal row History that silently recomputes itself is history you cannot trust. Files: `src/goals.ts` ### Done when - [ ] Contractions and hyphenated words count as one - [ ] Deleting days show additions, removals and a net - [ ] A rule change never rewrites historical rows - [ ] Scene counts sum to the manuscript total ### Watch out - Splitting on spaces counts a contraction as two words and a line with no spaces as one, and Unicode Annex 29 is explicit that its default word boundaries need tailoring for scripts that do not use spaces at all, so a count that works for English silently becomes nonsense in translation. Use a segmentation-aware count, write the rule into the goal row, and never recompute yesterday's numbers when the rule changes. ## Phase 4 · Finding the scene where the knife appears Search and backlinks work across the whole project and rebuild from the files. ### Steps 1. Derive links from the text Hand-maintained links rot by chapter four. Files: `src-tauri/src/links.rs` 2. Land results in the scene, at the match A result that opens a file and leaves you scrolling is half a feature. Files: `src/search/Results.tsx` 3. Rebuild the index to change the rules A rescan instead of a migration, every time. Files: `src-tauri/src/index.rs` ### Done when - [ ] A one-off phrase is found and opened at the match - [ ] Renaming and rescanning updates every backlink - [ ] A rebuilt index reproduces results exactly - [ ] Search time on a full manuscript is measured ### Watch out - Storing links as rows the writer has to maintain by hand means the links are right on the day they are made and wrong for the rest of the book, because a renamed character does not update anything. Derive links from the text at index time, so a rename is a find and replace followed by a rescan. ## Not in this build - Multi-device sync. - Collaboration and sharing. - Importers for proprietary project formats. - Model-generated writing suggestions. ## After v1, if you want it - A revision mode that compares two snapshots scene by scene, reusing what is already stored - A corkboard view over the manifest, which adds a view and no new source of truth - A compile step with configurable chapter headings, once the export path has been used in anger
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. The folder holding the scene files and the manifest. This is the manuscript. PROJECT_PATH=~/Documents/novel # Required. Disposable. Deleting it and rebuilding is a supported operation. INDEX_PATH=~/.local/share/novel/index.db # Required. Inside the project folder, so a backup of the folder covers them too. SNAPSHOT_DIR=~/Documents/novel/.snapshots # Required. Retention by count. Text compresses well, so keep more than feels necessary. SNAPSHOT_KEEP=50 # Required. Plus one before every operation that rewrites files. SNAPSHOT_INTERVAL_MINUTES=15 # Required. Idle, not per keystroke, and never during an input method composition. AUTOSAVE_IDLE_MS=1200 # Required. Stamped onto every goal row. Changing it never rewrites history. WORD_COUNT_RULE=uax29
# Novlr · product brief ## Problem Novlr's solo core is compact: build a local-first novel writing app with fast capture, full-text search, backlinks, tags, and Markdown export. A competent builder can reach a useful personal version in one sitting, while the paid product mainly wins on polish, sync, importers. ## Product outcome A drafting app where the manuscript is a folder you could open in any text editor, a crash costs nothing, the daily count means the same thing in month nine as in week one, and a restore has been tested every time the suite ran. ## 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 - Multi-device sync. - Collaboration and sharing. - Importers for proprietary project formats. - Model-generated writing suggestions. - conflict-safe multi-device sync - mature importers and exporters - collaborative editing and sharing - native mobile clients ## Success criteria - An index rebuild is confirmed to restore everything from the folder - A crash mid-save is confirmed to leave the previous file intact - Unsaved text is confirmed recoverable from the journal - A snapshot restore is confirmed to run in the suite - A counting rule change is confirmed not to rewrite historical rows
# Build brief · Novlr 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 a local-first novel drafting app whose manuscript is plain files on your own disk to replace Novlr. 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 The manuscript is years of somebody's life and this application is a convenience on top of it. So the words live in plain text files in a folder the writer chose, one file per scene, and the database is an index over those files that can be deleted at any moment and rebuilt. If the only copy of chapter nine is inside a SQLite blob, you have taken custody of something you are not equipped to keep. Everything else follows from that. Every save is written to a temporary file and moved into place, so a crash leaves the previous version rather than a truncated one. Snapshots are taken on a schedule and before every risky operation, retained by count, and restoring one is exercised in the test suite rather than described in the README. A writing tool that loses a session is not a writing tool that needs a better feature, it is one nobody should use again. ### Stack (fixed, do not substitute) - Shell: Tauri 2 with React and TypeScript · a desktop app with real filesystem access, because the files are the point - Truth: One plain text file per scene in a folder the writer picks · the manuscript has to outlive this application by decades - Index: SQLite holding structure, links and search, rebuildable from the folder · an index is disposable and a manuscript is not - Saving: Temporary file then atomic move, plus a crash journal · a half-written scene file is the failure this whole design avoids - Snapshots: Scheduled copies retained by count, restore tested in the suite · the cheapest insurance available and the one people skip ### Data model (create this before Phase 1) - project(path, title, author, created_at, index_version) · the folder is the project, nothing else is - scenes(id, relative_path, chapter_id, ordinal, title, word_count, checksum, modified_at) · mirrors a file, never replaces it - chapters(id, ordinal, title) and an order manifest written into the folder, so reordering is not renaming files - notes(id, kind, title, body_path) · characters, places and research, files in the same folder tree - links(from_scene_id, to_note_id, occurrences) · derived from the text at index time, which is why backlinks survive a rebuild - sessions(id, started_at, ended_at, words_added, words_removed, net_words) · additions and removals kept apart, because an editing day is not a failure - goals(day, target_words, counted_by, net_words) · counted_by records the counting rule in force, so a rule change never rewrites last month ### Phase 1 · Files on disk, index on the side Build: Pick a project folder, write one file per scene, and keep chapter order in a manifest file beside them. Save by writing a temporary file and moving it into place. Build the index by scanning the folder, and provide a command that deletes the index and rebuilds it. Trap: Reordering chapters by renaming the files means every reorder rewrites the folder, breaks any external backup's history and, on the day a rename fails halfway, leaves two scenes with the same name and one gone. Keep the order in a manifest and let the filenames stay still. Done when: Deleting the index and rebuilding restores every scene, chapter order, note and backlink, exercised in the suite. Killing the process during a save leaves the previous file intact and readable, tested by injecting a crash mid-write. Editing a scene in an external text editor is picked up on the next scan without losing the external change. Reordering chapters changes the manifest and renames nothing. Do not build yet: No editor niceties, word counting, search or export. ### Phase 2 · An editor that never eats a keystroke Build: Autosave on idle and on losing focus rather than on every keystroke, append every change to a crash journal between saves, and recover from the journal on launch. Never write while an input method composition is in progress. Trap: Saving on every keystroke while an input method editor is mid-composition writes the half-composed characters into the file, so a writer using a Japanese, Chinese or Vietnamese input method finds their text corrupted in ways that look like the app is broken, because it is. Commit only on composition end, and debounce the save to idle rather than to keys. Done when: Text typed and never explicitly saved is recovered after the process is killed, tested by killing it mid-paragraph. Composition through an input method produces the same file content as the same text typed directly, tested with a composition sequence. Autosave never blocks input, measured by typing continuously through several save cycles. The journal is truncated once its content is safely in the file, so it cannot grow without bound. Do not build yet: No counting, search, links or export yet. ### Phase 3 · Counting words without lying about them Build: Count words with a rule you write down: word boundaries as the Unicode segmentation annex defines them, so that contractions and hyphenated words are one word each. Track additions and removals separately per session, and store the counting rule with every daily goal row. Trap: Splitting on spaces counts a contraction as two words and a line with no spaces as one, and Unicode Annex 29 is explicit that its default word boundaries need tailoring for scripts that do not use spaces at all, so a count that works for English silently becomes nonsense in translation. Use a segmentation-aware count, write the rule into the goal row, and never recompute yesterday's numbers when the rule changes. Done when: A contraction and a hyphenated word each count as one word, asserted against a fixed test string. A day where more words were deleted than added shows both figures and a negative net, rather than a zero or a failure. Changing the counting rule changes today's figure and leaves every historical goal row untouched, tested by switching rules. The manuscript total agrees with the sum of scene counts to the word. Do not build yet: No search, links or export yet. ### Phase 4 · Finding the scene where the knife appears Build: Index scene text for search, derive links to character and place notes from mentions in the text, and show backlinks on each note. Search results land in the scene at the match. Make the whole index rebuildable so a change to the indexing rules is a rescan. Trap: Storing links as rows the writer has to maintain by hand means the links are right on the day they are made and wrong for the rest of the book, because a renamed character does not update anything. Derive links from the text at index time, so a rename is a find and replace followed by a rescan. Done when: A phrase appearing once in one scene is found and the editor opens at the match. Renaming a character and rescanning updates every backlink without any manual step, tested end to end. Deleting the index and rebuilding produces identical search results and identical backlinks. Search over a full-length manuscript returns in a time that is measured and recorded in the README. Do not build yet: No export or snapshot management yet. ### Phase 5 · Snapshots, export, and walking away Build: Take snapshots on a schedule and before every operation that rewrites files, retained by count with a restore exercised in the suite. Export the whole manuscript as one document with chapter breaks, as a folder of files, and in a format a publishing editor can open. Document how to move the project folder to another machine. Trap: A snapshot feature nobody has ever restored from is a folder of files with an unknown format and an untested code path, and the first restore always happens on the worst day. Restore in the test suite, on every run, from a snapshot the suite just took. Done when: A snapshot is restored in the test suite on every run and the restored project matches the original scene for scene. Export produces a single document with chapter breaks whose word count matches the manuscript total. Moving the project folder to another machine and opening it there rebuilds the index and loses nothing, exercised manually once and documented. Snapshot retention is by count and is stated in the README. Do not build yet: Nothing after this. ### Out of scope (and why) - Multi-device sync, which is a conflict resolution problem that has eaten larger projects than this and would put the manuscript at risk to solve. - Collaborative editing and sharing, which needs a server and turns a private drafting tool into a service. - Mature importers from other writing applications, since each proprietary project format is its own reverse engineering exercise. - Writing suggestions from a model, which is a separate product decision and not one to make by accident in a first release. ### README must contain - That the manuscript is plain files in a folder you choose and the database is a disposable index - That every save is atomic and a crash leaves the previous version rather than a truncated one - How snapshots work, how many are kept, and that a restore runs in the test suite on every run - The word counting rule in use, and that changing it never rewrites historical goal rows - How to move the project folder to another machine and what happens on first open there
# Architecture · Novlr ## Stack | Part | Choice | Why | | --- | --- | --- | | Shell | Tauri 2 with React and TypeScript | a desktop app with real filesystem access, because the files are the point | | Truth | One plain text file per scene in a folder the writer picks | the manuscript has to outlive this application by decades | | Index | SQLite holding structure, links and search, rebuildable from the folder | an index is disposable and a manuscript is not | | Saving | Temporary file then atomic move, plus a crash journal | a half-written scene file is the failure this whole design avoids | | Snapshots | Scheduled copies retained by count, restore tested in the suite | the cheapest insurance available and the one people skip | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Files | Scene files, the manifest and atomic saves. | Not swappable for storing text in the database, which is the one decision this build refuses. | | Index | Structure, links and search, rebuilt by scanning. | Any index, provided it stays disposable. | | Editor | Autosave timing, journalling and composition safety. | Any editor component, provided composition end is the commit point. | | Counting | Segmentation-aware counts and per-rule history. | Any rule, provided it is stamped onto rows rather than applied retroactively. | | Snapshots | Scheduled copies and a tested restore. | Any mechanism, provided the restore runs in the suite. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `PROJECT_PATH` · required · The folder holding the scene files and the manifest. This is the manuscript. - `INDEX_PATH` · required · Disposable. Deleting it and rebuilding is a supported operation. - `SNAPSHOT_DIR` · required · Inside the project folder, so a backup of the folder covers them too. - `SNAPSHOT_KEEP` · required · Retention by count. Text compresses well, so keep more than feels necessary. - `SNAPSHOT_INTERVAL_MINUTES` · required · Plus one before every operation that rewrites files. - `AUTOSAVE_IDLE_MS` · required · Idle, not per keystroke, and never during an input method composition. - `WORD_COUNT_RULE` · required · Stamped onto every goal row. Changing it never rewrites history. ## 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 · Novlr product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Tauri 2 with React and TypeScript, One plain text file per scene in a folder the writer picks, SQLite holding structure, links and search, rebuildable from the folder, Temporary file then atomic move, plus a crash journal, Scheduled copies retained by count, restore tested in the suite. - 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 - Reordering chapters by renaming the files means every reorder rewrites the folder, breaks any external backup's history and, on the day a rename fails halfway, leaves two scenes with the same name and one gone. Keep the order in a manifest and let the filenames stay still. - Saving on every keystroke while an input method editor is mid-composition writes the half-composed characters into the file, so a writer using a Japanese, Chinese or Vietnamese input method finds their text corrupted in ways that look like the app is broken, because it is. Commit only on composition end, and debounce the save to idle rather than to keys. - Splitting on spaces counts a contraction as two words and a line with no spaces as one, and Unicode Annex 29 is explicit that its default word boundaries need tailoring for scripts that do not use spaces at all, so a count that works for English silently becomes nonsense in translation. Use a segmentation-aware count, write the rule into the goal row, and never recompute yesterday's numbers when the rule changes. - Storing links as rows the writer has to maintain by hand means the links are right on the day they are made and wrong for the rest of the book, because a renamed character does not update anything. Derive links from the text at index time, so a rename is a find and replace followed by a rescan. - A snapshot feature nobody has ever restored from is a folder of files with an unknown format and an untested code path, and the first restore always happens on the worst day. Restore in the test suite, on every run, from a snapshot the suite just took.
# Delivery milestones · Novlr Estimated effort: **weekend** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Files on disk, index on the side Scenes are plain files, the database is rebuildable, and a crash never truncates one. ### Steps 1. Write through a temporary file and move The single change that separates a safe writing tool from a risky one. Files: `src-tauri/src/save.rs`, `src-tauri/tests/atomic_save.rs` 2. Keep order in a manifest Filenames stay put, so backups and version control stay legible. Files: `src-tauri/src/manifest.rs` 3. Make the index rebuildable and prove it The test that says out loud which artefact is the manuscript. Files: `src-tauri/src/index.rs`, `src-tauri/tests/rebuild.rs` ### Done when - [ ] An index rebuild restores everything from the folder - [ ] A crash mid-save leaves the previous file intact - [ ] External edits are picked up without loss - [ ] Reordering renames no files ### Watch out - Reordering chapters by renaming the files means every reorder rewrites the folder, breaks any external backup's history and, on the day a rename fails halfway, leaves two scenes with the same name and one gone. Keep the order in a manifest and let the filenames stay still. ## M2 · An editor that never eats a keystroke Typing is never interrupted by a save, and nothing is lost between saves. ### Steps 1. Debounce saves to idle and blur Saving per keystroke is how an editor starts stuttering at fifty thousand words. Files: `src/editor/autosave.ts` 2. Journal changes between saves The journal is what turns a crash into a shrug. Files: `src-tauri/src/journal.rs`, `src-tauri/tests/recover.rs` 3. Never write mid-composition Composition end is the only safe commit point for several writing systems. Files: `src/editor/composition.ts` ### Done when - [ ] Unsaved text is recovered after a kill - [ ] Composed text matches directly typed text - [ ] Typing is never blocked by a save - [ ] The journal is truncated after a successful save ### Watch out - Saving on every keystroke while an input method editor is mid-composition writes the half-composed characters into the file, so a writer using a Japanese, Chinese or Vietnamese input method finds their text corrupted in ways that look like the app is broken, because it is. Commit only on composition end, and debounce the save to idle rather than to keys. ## M3 · Counting words without lying about them The count is defined once, recorded with the rule used, and an editing day is not a defeat. ### Steps 1. Count by word boundaries, not by spaces The annex exists because this is harder than it looks. Files: `src/count/words.ts`, `src/count/words.test.ts` 2. Keep additions and removals apart A day spent cutting is work, and a tool that scores it as zero teaches the wrong habit. Files: `src/sessions.ts` 3. Stamp the rule onto every goal row History that silently recomputes itself is history you cannot trust. Files: `src/goals.ts` ### Done when - [ ] Contractions and hyphenated words count as one - [ ] Deleting days show additions, removals and a net - [ ] A rule change never rewrites historical rows - [ ] Scene counts sum to the manuscript total ### Watch out - Splitting on spaces counts a contraction as two words and a line with no spaces as one, and Unicode Annex 29 is explicit that its default word boundaries need tailoring for scripts that do not use spaces at all, so a count that works for English silently becomes nonsense in translation. Use a segmentation-aware count, write the rule into the goal row, and never recompute yesterday's numbers when the rule changes. ## M4 · Finding the scene where the knife appears Search and backlinks work across the whole project and rebuild from the files. ### Steps 1. Derive links from the text Hand-maintained links rot by chapter four. Files: `src-tauri/src/links.rs` 2. Land results in the scene, at the match A result that opens a file and leaves you scrolling is half a feature. Files: `src/search/Results.tsx` 3. Rebuild the index to change the rules A rescan instead of a migration, every time. Files: `src-tauri/src/index.rs` ### Done when - [ ] A one-off phrase is found and opened at the match - [ ] Renaming and rescanning updates every backlink - [ ] A rebuilt index reproduces results exactly - [ ] Search time on a full manuscript is measured ### Watch out - Storing links as rows the writer has to maintain by hand means the links are right on the day they are made and wrong for the rest of the book, because a renamed character does not update anything. Derive links from the text at index time, so a rename is a find and replace followed by a rescan. ## M5 · Snapshots, export, and walking away (production only) A bad day is recoverable and the manuscript leaves in a form an editor will accept. ### Steps 1. Restore from a snapshot in the suite Every run, from a snapshot taken in that run. Otherwise it is a hope. Files: `src-tauri/tests/snapshot_restore.rs` 2. Export one document and a folder One for the editor, one for the writer, both from the same files. Files: `src/export/manuscript.ts` 3. Document moving the folder The project is a folder, and the README should make that obvious enough to be reassuring. Files: `README.md` ### Done when - [ ] A snapshot restore runs in the suite every time - [ ] Export word count matches the manuscript total - [ ] Moving the folder loses nothing - [ ] Snapshot retention is stated and enforced ### Watch out - A snapshot feature nobody has ever restored from is a folder of files with an unknown format and an untested code path, and the first restore always happens on the worst day. Restore in the test suite, on every run, from a snapshot the suite just took.
# Operations · Novlr ## Backup The project folder, by whatever already backs up your documents, with the snapshots living inside it so one arrangement covers both. The index is deliberately not worth backing up. ## Restore Restore the folder, open it, let the index rebuild, and check the manuscript total against the last figure you remember. That total is the quickest integrity check available and it takes a second. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring For a local application the checks live in the suite: a snapshot restore on every run, an index rebuild that reproduces search results, and a crash injected mid-save leaving the previous file intact. ## Incident checklist The incident in this category is always the same one, a lost session, and the answers are the journal for minutes, the snapshots for hours and the folder backup for days. If all three fail together the design was wrong, which is why each is tested separately. 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 - [ ] An index rebuild is confirmed to restore everything from the folder - [ ] A crash mid-save is confirmed to leave the previous file intact - [ ] Unsaved text is confirmed recoverable from the journal - [ ] A snapshot restore is confirmed to run in the suite - [ ] A counting rule change is confirmed not to rewrite historical rows ## Launch constraint Do not market omitted Novlr 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. The folder holding the scene files and the manifest. This is the manuscript. PROJECT_PATH=~/Documents/novel # Required. Disposable. Deleting it and rebuilding is a supported operation. INDEX_PATH=~/.local/share/novel/index.db # Required. Inside the project folder, so a backup of the folder covers them too. SNAPSHOT_DIR=~/Documents/novel/.snapshots # Required. Retention by count. Text compresses well, so keep more than feels necessary. SNAPSHOT_KEEP=50 # Required. Plus one before every operation that rewrites files. SNAPSHOT_INTERVAL_MINUTES=15 # Required. Idle, not per keystroke, and never during an input method composition. AUTOSAVE_IDLE_MS=1200 # Required. Stamped onto every goal row. Changing it never rewrites history. WORD_COUNT_RULE=uax29
$ choose a build depth, inspect the files, then open the complete pack in your agent
Novlr: 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
Novlr pricing
| plan | monthly | annual (per mo) | what you get |
|---|---|---|---|
| free | $0/user | $0/user | 2 writing projects; cross-device sync, notes/world-building, analytics, comments and basic Academy access |
| starter | — | $8/user | Unlimited projects; 30-day version history; goals/streaks; full Academy and community benefits |
| studio | — | $16/user | Unlimited projects/history; proofreader and advanced grammar/style; 3 extra themes; Google Drive/Dropbox backups; Novlr Sites included |
| lifetime studio member | custom | — | One-time $499; Studio forever plus co-ownership/member benefits |
| novlr sites add-on | $6/user | $5/user | Author website, premium themes, custom domain and unlimited republishing; included in Studio |
free tier2 writing projects; no numeric word, device or storage cap published
billingStarter/Studio prices published on annual billing only; Sites add-on monthly + annual; $499 lifetime Studio
hidden costsNovlr Sites costs extra on Free and Starter ($6/month or $60/year) but is included in Studio. No-commitment monthly prices for Starter/Studio did not render despite the page showing a billing-period control.
verified 2026-08-13 · source ↗
Vibecode Novlr
Yes. A competent AI coding agent (Claude Code, Codex, Cursor) can build a usable personal Novlr replacement in one session with the prompt on this page. It runs on your own machine or server with no subscription.
How much does Novlr cost?
Novlr's pricing is usage-based or varies by plan · The official pricing URL is recorded, but a stable current amount was not safely recoverable in this pass. Do not publish a number until rechecked..
What do I lose by replacing Novlr?
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 Novlr?
Yes: Reedsy Studio (A browser manuscript studio with cloud backup, one real goal and no invoice.) The prompt is for when you want it exactly your way.