Build Ulysses

YESreplaces $5.99/mosaves $71.88/yrback to the verdict

0%0 of 20 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 writing environment over a folder of Markdown files: a sheet library with groups and filters, a typewriter-style editor, word and character goals with honest progress, optional editing passes shown as a diff, and export to formatted documents with the source always readable elsewhere.

estimated effort weekendthe files for this build are in the project pack

ShellTauri with a TypeScript front endFilesPlain Markdown, one file per sheet, in a folder you chooseIndexSQLite for search, groups and goals, treated as disposableEditorA text editor built on a code editing component with Markdown syntaxExportMarkdown to formatted output through a document toolchain

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 front end and the tooling.

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

    Verify node --version prints v22 or higher

  2. installfree

    Why Tauri compiles the desktop shell and the file watcher.

    Get it rustup from rustup.rs, plus the platform packages Tauri lists. open ↗

    Verify A Tauri hello world builds and runs

  3. installfree

    Why Phase 5 turns Markdown into the formats people actually accept. Writing a converter is not the project.

    Get it Install pandoc or an equivalent, plus a typesetting engine if you want PDF output.

    Verify A test Markdown file converts to your target format

  4. have readyfree

    Why Ordering, search and goals are all trivial at five sheets and revealing at fifty, and compile order in particular fails only at scale.

    Get it Use your own writing, or split a long public domain text into sheets.

    Verify The library loads and its sheet count is reported

  5. installfree

    Why Phase 6 verifies the library reads without this application, which is the ownership claim and cannot be tested with the application that wrote it.

    Get it Any general Markdown editor you already have.

    Verify The library folder opens and sheets read correctly

Data model

Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.

- sheets(id, path, title, group_id, position, word_count, char_count, mtime, content_hash) · one Markdown file each
- groups(id, parent_id, name, position) · directories on disk, so the structure is visible outside the application
- goals(id, target_kind, target_value, scope_kind, scope_id, deadline) · scope is a sheet or a group
- sessions(id, started_at, ended_at, words_added, sheet_id) · writing sessions, which is what progress is actually made of
- keywords(id, name), sheet_keywords(sheet_id, keyword_id) · stored in front matter so they travel with the file
- Groups are directories and keywords are front matter, so the organisation survives without this application.

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
LIBRARY_PATHrequired~/Writing/LibraryThe folder of Markdown files. Your data, in your backup.
INDEX_PATHrequired~/.local/share/write/index.dbSearch and goals. Deliberately outside the library so syncing cannot corrupt it.
SAVE_DEBOUNCE_MSrequired400How long after typing before writing. Lower loses less on a crash.
EXPORT_DIRrequired~/Writing/ExportsWhere compiled documents are written. Never the library folder.
GRAMMAR_ENDPOINToptionalhttp://localhost:8081Optional local grammar service. Leave unset and the editing passes are disabled entirely.

The build, in order

  1. A library over a folder

    Your Markdown files appear as a browsable library, and the structure on disk matches what you see.

    1. The structure on disk is the structure in the application. No hidden database-only organisation.

      Files src/library.ts

      terminal
      npm create tauri-app@latest
    2. So they travel with the file and are visible in any editor.

      Files src/frontmatter.ts

    3. Hash what you write and ignore your own events, or the watcher will reload under the cursor.

      Files src/watcher.ts

    done when · tick each as it passes
    watch out
    • Deriving a sheet's title from its filename and then letting the user rename the title in the interface creates an ambiguity: does the file move? Decide explicitly, put the title in front matter, and leave filenames alone unless the user renames them deliberately.
  2. An editor that stays out of the way

    Writing feels quiet, and nothing is ever lost.

    1. Highlighting rather than rendering. A rich text surface fights Markdown and loses.

      Files src/editor.ts

    2. Temporary file plus rename. Test by killing the process mid-save.

      Files src/save.ts

    3. Incrementally or off the main thread. Recounting a long sheet on every keystroke is felt immediately.

      Files src/count.ts

    done when · tick each as it passes
    watch out
    • Writing directly over the file means a crash mid-save can truncate a chapter, which for a writer is the worst possible failure. Write to a temporary file and rename over the target, which is atomic and cannot leave a half-written sheet.
  3. Goals that are honest

    Progress is measured from what you actually wrote, including the days you did not.

    1. Both numbers. A day of cutting is progress and the interface should not disagree.

      Files src/sessions.ts

    2. Sheet or group, computed from current counts rather than a running total that can drift.

      Files src/goals.ts

    3. No streaks, no grades, no red. A missed deadline is a fact, not a verdict.

      Files src/ui/Goal.tsx

    done when · tick each as it passes
    watch out
    • Counting net words means a day spent cutting shows as negative progress, which is exactly backwards: editing is writing. Track words added and words removed separately, and show both rather than a single number that punishes revision.
  4. Editing passes with a visible diff

    Suggested changes are shown before they are applied, and the original survives.

    1. A local grammar service keeps the manuscript on the machine. Say clearly if any pass does not.

      Files src/passes/

    2. Sentence-level diffs on prose are unreadable. Word level with sentence grouping is what a writer can judge.

      Files src/diff.ts

    3. On the suggestion record, so acceptance is reversible beyond undo.

      Files src/suggestions.ts

    done when · tick each as it passes
    watch out
    • Applying a change directly and relying on undo loses the original as soon as anything else is typed, and a writer who accepts a rewrite and then dislikes it has no way back. Store the original with the suggestion, so acceptance remains reversible beyond the undo stack.
  5. Export

    A group of sheets becomes one formatted document, and the source is untouched.

    1. The stored position, not the filename. Test with a group where the two disagree.

      Files src/export.ts

    2. Include or exclude, so drafts and notes can live in the same group as the manuscript.

      Files src/export.ts

    3. Checksum before and after as a test.

      Files src/export.test.ts

    done when · tick each as it passes
    watch out
    • Compiling in filesystem order rather than the library's explicit order will reorder a book, and the mistake is invisible until someone reads the export. Store an explicit position and use it, since alphabetical filenames stop working at chapter ten.
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 writing tools.