Build 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.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree
Why Builds the interface half of the desktop app.
Get it nodejs.org, or nvm install 22. open ↗
Verify
node --version prints v22 or higher - installfree
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. open ↗
Verify
cargo --version prints a version and a bare Tauri app builds - have readyfree, 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 - have readyfree
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 - decidefree
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
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- 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
Environment variables
These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
PROJECT_PATH | required | ~/Documents/novel | The folder holding the scene files and the manifest. This is the manuscript. |
INDEX_PATH | required | ~/.local/share/novel/index.db | Disposable. Deleting it and rebuilding is a supported operation. |
SNAPSHOT_DIR | required | ~/Documents/novel/.snapshots | Inside the project folder, so a backup of the folder covers them too. |
SNAPSHOT_KEEP | required | 50 | Retention by count. Text compresses well, so keep more than feels necessary. |
SNAPSHOT_INTERVAL_MINUTES | required | 15 | Plus one before every operation that rewrites files. |
AUTOSAVE_IDLE_MS | required | 1200 | Idle, not per keystroke, and never during an input method composition. |
WORD_COUNT_RULE | required | uax29 | Stamped onto every goal row. Changing it never rewrites history. |
The build, in order
Files on disk, index on the side
Scenes are plain files, the database is rebuildable, and a crash never truncates one.
The single change that separates a safe writing tool from a risky one.
Files
src-tauri/src/save.rssrc-tauri/tests/atomic_save.rsFilenames stay put, so backups and version control stay legible.
Files
src-tauri/src/manifest.rsThe test that says out loud which artefact is the manuscript.
Files
src-tauri/src/index.rssrc-tauri/tests/rebuild.rs
done when · tick each as it passeswatch 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.
An editor that never eats a keystroke
Typing is never interrupted by a save, and nothing is lost between saves.
Saving per keystroke is how an editor starts stuttering at fifty thousand words.
Files
src/editor/autosave.tsThe journal is what turns a crash into a shrug.
Files
src-tauri/src/journal.rssrc-tauri/tests/recover.rsComposition end is the only safe commit point for several writing systems.
Files
src/editor/composition.ts
done when · tick each as it passeswatch 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.
Counting words without lying about them
The count is defined once, recorded with the rule used, and an editing day is not a defeat.
The annex exists because this is harder than it looks.
Files
src/count/words.tssrc/count/words.test.tsA day spent cutting is work, and a tool that scores it as zero teaches the wrong habit.
Files
src/sessions.tsHistory that silently recomputes itself is history you cannot trust.
Files
src/goals.ts
done when · tick each as it passeswatch 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.
Finding the scene where the knife appears
Search and backlinks work across the whole project and rebuild from the files.
Hand-maintained links rot by chapter four.
Files
src-tauri/src/links.rsA result that opens a file and leaves you scrolling is half a feature.
Files
src/search/Results.tsxA rescan instead of a migration, every time.
Files
src-tauri/src/index.rs
done when · tick each as it passeswatch 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.
Snapshots, export, and walking awayproduct builder
A bad day is recoverable and the manuscript leaves in a form an editor will accept.
Every run, from a snapshot taken in that run. Otherwise it is a hope.
Files
src-tauri/tests/snapshot_restore.rsOne for the editor, one for the writer, both from the same files.
Files
src/export/manuscript.tsThe project is a folder, and the README should make that obvious enough to be reassuring.
Files
README.md
done when · tick each as it passeswatch 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.
That is the whole plan for Novlr. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- 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
- 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
Need the files? The project pack on the verdict page hands your agent the whole brief · more notes & knowledge.