Build Supernotes

YESreplaces $10.82/mosaves $129.84/yrback 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 card based notebook whose hard problems are keeping files as the only source of truth with a disposable index, naming those files so they survive every filesystem your notes will ever touch, and handling edits made outside the application without ever overwriting one.

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

ShellTauri 2 with React and TypeScriptStorageOne Markdown file per card with front matter, in a folder you pickIndexSQLite, derived only, rebuilt from the folder by one commandIdentityA stable id in front matter, independent of the filename and the titleWritesTemporary file, flush, atomic replace, with the watcher ignoring its own writes

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 Tauri needs both, and finding out during the first build is an avoidable hour.

    Get it nodejs.org and rustup.rs, plus the Tauri prerequisites for your platform. open ↗

    Verify A fresh Tauri template builds and opens a window

  2. have readyfree

    Why Filename sanitising, link resolution and rebuild all behave differently on two hundred real cards than on five invented ones.

    Get it Export an existing notebook to Markdown, or copy a folder of notes you already keep.

    Verify The folder indexes without errors and the card count matches the file count

  3. have readyfree

    Why The filename rules only prove themselves on a filesystem with different rules from the one you develop on, and a virtual machine is enough.

    Get it A Windows or macOS virtual machine, or a colleague willing to copy a folder and open it.

    Verify The fixture vault copies across and every card opens

  4. installfree

    Why The claim that the folder is portable is only true if another tool renders it, and that is a five minute check nobody does unless it is a prerequisite.

    Get it Any Markdown editor that opens a folder.

    Verify Cards, front matter and relative images all render in it

Data model

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

- card file front matter(id, title, tags, created, updated, pinned) · the file is the record and everything else is derived from it
- cards(id, path, title, tags_json, created_at, updated_at, content_hash, missing_since) · an index row, rebuildable at any time
- links(from_id, to_id, display_text, ordinal, resolved) · unresolved links are kept and shown rather than dropped
- attachments(card_id, relative_path, sha256, bytes) · stored beside the cards and referenced by relative path only
- conflicts(id, card_id, path, detected_at, resolved_at, kept) · an external edit that clashes becomes a second file, never an overwrite
- index_state(rebuilt_at, cards_indexed, schema_version) · so you can see when the derived data was last derived
- Every table except the front matter can be deleted and rebuilt, which is the test of whether the files really are the source of truth.

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
VAULT_PATHrequired~/NotesThe folder of Markdown files. This is all of the user's data.
INDEX_PATHrequired~/.local/share/cards/index.dbDerived only. Deleting it must cost nothing but time.
MAX_FILENAME_LENGTHrequired120Leaves headroom under the platform path limit including the vault prefix.
UNICODE_NORMALISATIONrequiredNFCApplied on write so one accented title cannot become two cards.
CONFLICT_SUFFIXrequired.conflictAppended to the copy kept when an external edit clashes.
WATCH_DEBOUNCE_MSrequired200Coalesces editor save storms. It is not how own writes are ignored.

The build, in order

  1. Files are the truth, the index is disposable

    Deleting the database and rebuilding loses nothing.

    1. The order is the guarantee: a crash between the two costs an index entry, not a note.

      Files src/cards/write.ts

    2. Because the title is the thing most likely to change.

      Files src/cards/id.ts

    3. If rebuild is not in the suite, the index quietly becomes load bearing.

      Files src/index/rebuild.tstests/rebuild.test.ts

    done when · tick each as it passes
    watch out
    • Keeping the card body in SQLite and writing Markdown only on export means the export is a feature that can break, so the day it breaks the notes are inside a database file the person cannot read. Write the file first, index second, and treat the index as a cache you are willing to delete at any moment.
  2. Names that survive every filesystem

    A card title can contain anything and the filename still works everywhere.

    1. Reserved characters, reserved device names, trailing space and period, path length.

      Files src/files/sanitise.tstests/filenames.test.ts

    2. Otherwise one accented title becomes two cards after it crosses a filesystem.

      Files src/files/sanitise.ts

    3. The filename is a convenience; the title is the data.

      Files src/cards/write.ts

    done when · tick each as it passes
    watch out
    • Naming files straight from card titles produces notes that are fine on your laptop and unopenable the moment the folder touches Windows, which forbids the characters < > : " / \ | ? *, treats CON, PRN, AUX, NUL, COM1 to COM9 and LPT1 to LPT9 as devices even with an extension added, refuses trailing spaces and periods, and historically caps a path at 260 characters. Sanitise on write, keep the real title in front matter, and test the whole list.
  3. Links that survive a rename

    Renaming a card never breaks a link to it.

    1. Two pieces of information, and only one of them is allowed to change.

      Files src/links/parse.ts

    2. Visibly, with the id, so it can be repaired rather than guessed at.

      Files src/links/render.tsx

    3. Derived means a rebuild fixes them, which is the point of the index.

      Files src/links/backlinks.tstests/backlinks.test.ts

    done when · tick each as it passes
    watch out
    • Linking by title means every rename silently breaks every link that pointed at the old one, and because links are only followed occasionally the damage is discovered months later across dozens of cards. Link by id, keep the title as display text, and let a rename update the text without touching the target.
  4. Edits from outside, without losing one

    A card changed in another editor is picked up and never overwritten.

    1. Not by a timer, which is how real edits get eaten.

      Files src/watch/index.ts

    2. Two files and a comparison beats a merge that guessed.

      Files src/conflicts.tstests/conflict.test.ts

    3. Temporary file, flush, replace, for every single save.

      Files src/files/atomic.ts

    done when · tick each as it passes
    watch out
    • A watcher that reacts to its own writes loops forever and rewrites files until something gives up, and the usual quick fix of ignoring events for a second turns into losing a real external edit that arrived inside that second. Track the hash you just wrote and ignore an event only when the content matches it exactly.
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.