A clean notes app for Möbius. Markdown renders live as you type, with checklists, images and file attachments, LaTeX math, pinning, per-note color, locking, and instant search. Notes work offline and receive automatic local git snapshots.
Möbius compiles the source tree directly. index.jsx is only the stable entry;
the maintainable implementation lives under src/:
npm install
npm test # unit and render-harness regressions
npm run verify:mobiusThere is no generated application bundle in the repository. Edit
src/app.jsx or src/{lib,ui,editor}/*; Möbius resolves those declared source
files when the app is applied or installed.
src/lib/— note model and hashing, storage paths, the serialized collection for closed notes, content-addressed attachments and GC leases, derived index cache, legacy Markdown migration, preview sanitizing, search, and math parsing.src/ui/— grid, cards, editor panel, colors, confirmation modal, icons, and the app's single stylesheet.src/editor/— CodeMirror extensions, live-preview decorations, widgets, and the React wrapper.
Runtime data lives under /data/apps/<numeric-app-id>/:
notes/<noteId>.json canonical { meta, body } document
attachments/<sha256>.<ext> content-addressed image/file blobs
index.json derived grid cache; never authoritative
notes-meta.json self-describing data contract
signals.jsonl text-free app activity signals
.git/ local note snapshot history
The body field is plain Markdown. A startup migration safely converts legacy
notes/<id>.md files and removes each legacy file only after its JSON replacement
is server-confirmed.
The open note uses the platform's useDocument(..., { mode: 'lww' }) primitive.
Closed-note updates use a small serialized last-write-wins collection over the
same storage contract. Notes deliberately adds no second merge engine, conflict
descriptor state machine, or agent resolver.
A write resolves only when it is either server-synced or durably queued in the platform's offline outbox. Fatal refusals remain visible as Save failed, keep the editor open, and are retried rather than reported as saved. Queued writes are available immediately through the runtime's read-your-writes overlay and drain after reconnection.
Concurrent writes to the same note are last-write-wins. When the platform publishes a newer value, the open editor adopts it verbatim. A small local-echo guard prevents an older optimistic save response from moving the cursor backward when typing has already continued; it is not a merge or conflict subsystem.
A cold offline load paints index.json placeholders. Because enumeration may be
unavailable offline, the app keeps those placeholders until reconnection instead
of replacing them with a misleading empty state.
Attachments are immutable and content-addressed. Every save carries both the existing attachment records and references found in the live editor body. In-flight leases and the open-body pin prevent garbage collection from deleting a blob before its note write becomes visible. GC skips entirely if authoritative note enumeration is unavailable.
job.sh runs every ten minutes. It is deterministic and agent-free: when note
content changed, it commits canonical notes and lightweight metadata to the data
directory's local git history. Derived files, activity signals, attachments,
temporary files, and legacy conflict/lease directories are ignored.
Notes emits text-free activity signals for Reflection: app_ready, item
created/updated/opened/deleted, attachment_added, search_no_results, and
error. Note text, search terms, and attachment filenames are never included.
See DESIGN.md for the current architecture and invariants.
MIT