Build Novlr

YESback to the verdict

0%0 of 17 items done

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

A 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 weekendthe files for this build are in the project pack

ShellTauri 2 with React and TypeScriptTruthOne plain text file per scene in a folder the writer picksIndexSQLite holding structure, links and search, rebuildable from the folderSavingTemporary file then atomic move, plus a crash journalSnapshotsScheduled copies retained by count, restore tested in the suite

Before step 1

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

  1. installfree

    Why Builds the interface half of the desktop app.

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

    Verify node --version prints v22 or higher

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

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

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

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

VariableNeededExampleWhere the value comes from
PROJECT_PATHrequired~/Documents/novelThe folder holding the scene files and the manifest. This is the manuscript.
INDEX_PATHrequired~/.local/share/novel/index.dbDisposable. Deleting it and rebuilding is a supported operation.
SNAPSHOT_DIRrequired~/Documents/novel/.snapshotsInside the project folder, so a backup of the folder covers them too.
SNAPSHOT_KEEPrequired50Retention by count. Text compresses well, so keep more than feels necessary.
SNAPSHOT_INTERVAL_MINUTESrequired15Plus one before every operation that rewrites files.
AUTOSAVE_IDLE_MSrequired1200Idle, not per keystroke, and never during an input method composition.
WORD_COUNT_RULErequireduax29Stamped onto every goal row. Changing it never rewrites history.

The build, in order

  1. Files on disk, index on the side

    Scenes are plain files, the database is rebuildable, and a crash never truncates one.

    1. The single change that separates a safe writing tool from a risky one.

      Files src-tauri/src/save.rssrc-tauri/tests/atomic_save.rs

    2. Filenames stay put, so backups and version control stay legible.

      Files src-tauri/src/manifest.rs

    3. The 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 passes
    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.
  2. An editor that never eats a keystroke

    Typing is never interrupted by a save, and nothing is lost between saves.

    1. Saving per keystroke is how an editor starts stuttering at fifty thousand words.

      Files src/editor/autosave.ts

    2. The journal is what turns a crash into a shrug.

      Files src-tauri/src/journal.rssrc-tauri/tests/recover.rs

    3. Composition end is the only safe commit point for several writing systems.

      Files src/editor/composition.ts

    done when · tick each as it passes
    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.
  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.

    1. The annex exists because this is harder than it looks.

      Files src/count/words.tssrc/count/words.test.ts

    2. A day spent cutting is work, and a tool that scores it as zero teaches the wrong habit.

      Files src/sessions.ts

    3. History that silently recomputes itself is history you cannot trust.

      Files src/goals.ts

    done when · tick each as it passes
    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.
  4. Finding the scene where the knife appears

    Search and backlinks work across the whole project and rebuild from the files.

    1. Hand-maintained links rot by chapter four.

      Files src-tauri/src/links.rs

    2. A result that opens a file and leaves you scrolling is half a feature.

      Files src/search/Results.tsx

    3. A rescan instead of a migration, every time.

      Files src-tauri/src/index.rs

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

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