Build Scrivener
A manuscript binder whose hard problems are keeping every document a readable file on disk with the index rebuildable from it, importing out of a proprietary package without mangling the text, and compiling many documents into one manuscript the same way every time.
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 application.
Get it nodejs.org, or nvm install 22. open ↗
Verify
node --version prints v22 or higher - installfree
Why Runs the desktop shell and the file operations that have to be atomic.
Get it rustup.rs, then the platform packages listed in the Tauri prerequisites. open ↗
Verify
cargo --version prints a version and a bare Tauri app runs - have readyfree
Why The import bug that matters is invisible in English prose and obvious in any manuscript with a name in it, so the fixtures have to contain both.
Get it Save short documents from any word processor containing accented text, a non-Latin script and nested formatting, plus a plain text reference for each.
Verify
Each fixture imports byte identically to its reference - have readyfree
Why Search timing and index rebuild cost are invisible on a ten document test project and are the first thing a real novel exposes.
Get it Generate documents of realistic length with a script, committed as a fixture generator rather than as files.
Verify
Search and rebuild both stay inside the stated times - decidefree
Why Both are stated in the README and asserted by tests, and a counting rule invented per feature is how a writer stops trusting the numbers.
Get it Write down how hyphenated words, contractions and numerals count, and how long snapshots are kept.
Verify
Both are in the README and named in tests
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- projects(path, format_version, created_at, opened_at) · the folder is the project, the row is a convenience - documents(id, project_path, rel_path, title, ordinal, parent_id, kind, include_in_compile, label, status) - document files on disk: one Markdown file per document with YAML front matter carrying the same metadata - snapshots(document_id, taken_at, reason, path, bytes) · reason is manual, pre-compile, pre-import or pre-merge - targets(project_path, scope, words, deadline) · manuscript and session goals - search_index(document_id, content) · derived, dropped and rebuilt from the folder without asking - The folder is the source of truth. Deleting the database and reopening the project must reproduce the binder exactly, which is the test that keeps it true.
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 |
|---|---|---|---|
PROJECTS_DIR | required | ~/Documents/manuscripts | Where project folders live. Back this up, not the index. |
INDEX_PATH | required | ~/.local/share/binder/index.db | Derived. Safe to delete; it rebuilds from the folder. |
SNAPSHOT_RETENTION_DAYS | required | 180 | Never removes the most recent snapshot of a document. |
AUTOSAVE_SECONDS | required | 10 | Writes through a temporary file that is verified before replacing. |
BACKUP_DIR | required | ~/Documents/manuscripts-backups | Scheduled copies of the whole project folder. |
SEARCH_MAX_MS | required | 300 | The budget the search test enforces at project scale. |
The build, in order
A project you can read with any text editor
Every document is a file, and the index is derived from the folder rather than the reverse.
The database is an index, and an index can be thrown away.
Files
src/project/files.tssrc/project/index.tsThe only check that keeps the folder authoritative as the app grows.
Files
tests/rebuild-index.test.tsLinks and snapshots point at identifiers, not at filenames.
Files
src/project/identifiers.ts
done when · tick each as it passeswatch out- Keeping the text in the database and the folder as an export target means a corrupt database is a lost novel, and the writer discovers this on the morning the file will not open. Put the words in files, keep the database derived, and prove it by deleting and rebuilding the index in the tests.
Snapshots, and writes that cannot half happen
No operation can leave a document shorter than it was without a recoverable copy.
A truncated chapter is indistinguishable from a deleted one.
Files
src/storage/atomic.tsAutomatically, with the reason recorded.
Files
src/snapshots/take.tsThe recovery must not be the thing that loses the work.
Files
src/snapshots/restore.tstests/restore.test.ts
done when · tick each as it passeswatch out- Restoring a snapshot over the live document is how a writer loses this morning's work while trying to recover last week's, and it happens under exactly the stress that makes people click the wrong button. Restore into a new document beside the original and let the writer choose, and never delete on their behalf.
Getting a manuscript out of the original
Importing a real project produces the same words, not almost the same words.
The count is scoped to the group, and restoring it on exit is the whole trick.
Files
src/import/rtf.tssrc/import/rtf.test.tsOtherwise everything outside the common range is garbage.
Files
src/import/rtf.tsEyeballing an imported chapter proves nothing.
Files
tests/fixtures/chapter.rtftests/fixtures/chapter.txt
done when · tick each as it passeswatch out- RTF writes a non-ASCII character as a \uN escape followed by a fallback character for old readers, and a \ucN count says how many of those fallback characters to skip, scoped to the enclosing group and defaulting to one. A reader that ignores \uc emits both, so every accented word in an imported novel gains a stray letter, and since values above thirty two thousand seven hundred and sixty seven are written as negative numbers, a reader that treats them as unsigned mangles anything beyond the basic range. Keep a stack of counts and test with accented and non-Latin fixtures.
Compile: many documents, one manuscript, the same way every time
Compiling twice from unchanged documents produces byte identical output.
Flush first, snapshot, then read the folder.
Files
src/compile/compile.tsTwo identical inputs, two identical files, or something is reading state it should not.
Files
tests/compile-deterministic.test.tsA missing chapter should be a line in a report, not a surprise.
Files
src/compile/report.ts
done when · tick each as it passeswatch out- Building the manuscript from whatever the editor currently has in memory means a compile run while one document is unsaved silently drops a paragraph, and the file sent to an editor differs from the one on screen. Flush every document to disk, snapshot, then compile from the files only.
Finding things in a large project, and getting out of this oneproduct builder
Search stays fast at novel length, word counts are honest, and export needs no special tool.
It should never be the reason a project will not open.
Files
src/search/index.tsThree counts of the same chapter is worse than no count.
Files
src/stats/words.tssrc/stats/words.test.tsPlus a manifest, so it comes back in the same order.
Files
src/export/project.ts
done when · tick each as it passeswatch out- Counting words one way in the editor, another way in the target bar and a third way at compile leaves a writer who hit their daily goal seeing three different numbers, and they will trust none of them. Write one counting function, state its rule in the README, and call it from everywhere including the tests.
That is the whole plan for Scrivener. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Multi-device sync.
- Collaborative editing.
- A print layout engine.
- Writing back to the original project format.
- conflict-safe multi-device sync
- mature importers and exporters
- collaborative editing and sharing
- native mobile clients
- A corkboard view over the same binder data, which adds no new source of truth
- Cross-document links resolved by identifier, now that identifiers survive renames
- A second compile target such as an e-book format, added behind the existing ordering
Need the files? The project pack on the verdict page hands your agent the whole brief · more notes & knowledge.