Build Reflect

YESback to the verdict

0%0 of 16 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 notes app whose hard problems are links that survive a rename, search that answers while you type on ten thousand notes, and an export to plain files that does not mangle titles the operating system will not accept.

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

ShellTauri 2 with React and TypeScriptStorageSQLite, one file, with notes identified by an id and never by a titleLinksParsed on save into a links table, backlinks derived from itSearchAn index built on write with a stated latency budget asserted in testsExportMarkdown files with front matter carrying the id and the true title

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 runs the corpus and performance tests.

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

    Verify node --version prints v22 or higher

  2. installfree

    Why Tauri 2 needs it, and the global shortcut and warm capture window live on that side.

    Get it rustup.rs, then the platform build dependencies Tauri lists. open ↗

    Verify cargo --version prints a version and a dev build opens a window

  3. have readyfree

    Why Every performance claim in this build is meaningless on the twelve notes you wrote by hand, and the corpus is what makes the budgets falsifiable.

    Get it Write the generator in phase three, with realistic note lengths, link density and title collisions.

    Verify The corpus loads and the search and backlink tests run against it

  4. decidefree

    Why Search response and shortcut-to-cursor are the two numbers this app lives or dies by, and choosing them after measuring is just describing what you built.

    Get it Pick two numbers, write them in the documentation with the reason, and assert them in tests.

    Verify Both numbers appear in docs and in failing tests before the work starts

Data model

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

- notes(id, title, body, created_at, updated_at, deleted_at, daily_date) · id is stable for the note's life and never derived from the title
- aliases(note_id, alias, created_at) · every title a note has ever had, so an old link still resolves
- links(source_id, target_id, target_text, resolved, ordinal) · resolved false means a link to a note that does not exist yet
- tags(note_id, tag) · flat, because a hierarchy is a second feature pretending to be one
- search_index(note_id, ...) · rebuilt on write, with the shape chosen after measuring on a real corpus
- export_manifest(note_id, filename, title, exported_at, collision_suffix) · the mapping between ids and the names the filesystem allowed
- The title is a label. Everything that refers to a note refers to its id, which is why renaming is free and export needs a manifest.

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
DATA_DIRrequired~/.local/share/notesThe SQLite file and the search index. Back this up.
EXPORT_DIRrequired~/Documents/notes-exportMarkdown files and the manifest. Overwritten on each export.
CAPTURE_SHORTCUTrequiredCmdOrCtrl+Shift+NThe global shortcut. Conflicts with another app are the commonest setup failure.
SEARCH_BUDGET_MSrequired80Asserted in the suite against the generated corpus.
CAPTURE_BUDGET_MSrequired400Shortcut to typable cursor, measured from a cold application.
EXPORT_FILENAME_MODErequiredstrictStrict applies the Windows rules everywhere, so an export is portable between machines.

The build, in order

  1. Ids, not titles

    A note keeps its identity through every rename, and old links still resolve.

    1. Everything downstream depends on this one decision.

      Files src/notes/create.tssrc/db/schema.sql

    2. Renaming a note should not orphan the sentence that mentioned it.

      Files src/notes/rename.tstests/rename-links.test.ts

    3. Ambiguous links are worse than rejected ones.

      Files src/notes/validate.ts

    done when · tick each as it passes
    watch out
    • Keying notes by title, as the file-per-note tools do, means renaming one either breaks every link pointing at it or forces a rewrite of every file that mentions it, and the copies you already exported keep the old name forever. Give each note an id, keep the old titles as aliases, and let the rename be a single row update.
  2. Links and backlinks, computed once

    Backlinks appear instantly and a link to a note that does not exist yet is a first-class thing.

    1. One transaction, so a crash cannot leave half a graph.

      Files src/links/index.ts

    2. The moment that makes the unresolved link useful rather than annoying.

      Files src/links/resolve.tstests/resolve-on-create.test.ts

    3. Ten thousand notes, not the twelve you wrote by hand.

      Files tests/backlink-perf.test.ts

    done when · tick each as it passes
    watch out
    • Computing backlinks by scanning every note's body when a note is opened is fine at two hundred notes and unusable at five thousand, and because it degrades gradually you will not notice until the app you rely on daily has become slow. Write the links table on save and query it on read.
  3. Search that answers while you type

    A query over ten thousand notes returns ranked results inside a budget you wrote down first.

    1. A number chosen after measurement is a description, not a target.

      Files tests/search-perf.test.tsdocs/budgets.md

    2. Ten thousand notes with real length distribution and link density.

      Files scripts/generate-corpus.ts

    3. Never let indexing be a reason to delay a save.

      Files src/search/index.ts

    done when · tick each as it passes
    watch out
    • Building the index on read, or rebuilding it wholesale on every save, makes typing in a large note stutter, and the reflex fix of debouncing the save is how people lose the paragraph they just wrote. Index incrementally on write, off the interaction path, and never trade a save for a search.
  4. Capture in under a second

    A thought can be written before it is lost, from anywhere, with no navigation.

    1. The whole feature is the latency, and the latency is the warm window.

      Files src-tauri/src/capture.rs

    2. The number that decides whether anyone uses this.

      Files tests/capture-latency.test.ts

    3. One fewer decision between a thought and a record of it.

      Files src/capture/target.ts

    done when · tick each as it passes
    watch out
    • Making capture open the full application means a cold start, a window arranging itself, and a cursor that arrives second, which is long enough for the thought to go, so the feature quietly stops being used. Keep a warm capture window, focus the input before painting anything else, and measure the time from shortcut to typable cursor.
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.