Design decisions

Each record names a choice, the alternatives it beat, and what it costs. They are the same files as docs/adr/ in the repository, published as written.

  1. 001Stack and tooling

    TypeScript end to end, ESM only, latest stable everything, no backward-compatibility layers: Node 26 (built-in node:sqlite), TypeScript 7 (native compiler), pnpm 12 workspaces, Vite 8, Vitest 5,…

  2. 002SQLite is the source of truth; markdown files are a lossless mirror

    The graph lives in SQLite: an append-only op log plus state tables (page, block, blockprop, derived ref, blockfts, embedding). The same schema runs on the server (node:sqlite) and in the browser…

  3. 003Sync = op log + hybrid logical clocks + per-field last-writer-wins, server validates the tree

    Every write anywhere (editor, HTTP API, MCP tool, markdown import) is a list of ops: page.create|rename|prop|delete, block.create|place|text|prop|delete. An op targets one entity and one…

  4. 004Short time-ordered ids and fractional-index ordering

    Pages, blocks, and property definitions get 14-character ids in lowercase Crockford base32: 45 bits of milliseconds since the epoch (time-ordered, sortable, good for a thousand years) followed by…

  5. 005Client packaging: PWA first, Capacitor for stores, Tauri for desktop

    One Vite-built web client behind a small platform adapter (storage driver, keyboard insets, haptics, share, files, deep links, lifecycle). - v1 ships as an installable PWA (service worker,…

  6. 006Editor: rendered blocks plus one re-parented CodeMirror 6 surface

    All blocks render as HTML from our own inline markdown tokenizer. Exactly one editing surface exists at a time: a single CodeMirror 6 EditorView re-parented into the block being edited. The…

  7. 007Plugins: one package, optional server and client halves, trusted ESM in v1

    A plugin is a directory with a nooklet manifest in package.json (id, API version, optional server and client entries, JSON-schema settings, permissions, declared contributions), or a single…

  8. 008One operation registry for HTTP, MCP, and the typed client

    Stack: Hono 4 in one process (web client, /api/v1, /mcp, /openapi.json, /sync, /assets), Zod 4 schemas with generated JSON Schema, MCP SDK v2 (@modelcontextprotocol/server +…

  9. 009Every operation is a command; keybindings are user data

    A command is { id, title, description, category, when, defaultKeys, run }. Core registers every user-facing operation; plugins register theirs, declaratively in the manifest so they appear before…

  10. 010Embeddings via Ollama, vectors in sqlite-vec, hybrid search by rank fusion

    Server-only embeddings. Provider interface with an Ollama /api/embed implementation (default) and an OpenAI-compatible one; the model is a runtime setting, dimensions are discovered from the…

  11. 011Scheduling, repeats, and query blocks use one property/fence syntax, not org-mode

    Scheduled and deadline dates are ordinary typed properties, not org-mode drawer lines: scheduled:: 2026-09-12, deadline:: 2026-09-14 14:00 (ISO date, optional time, no weekday, no angle brackets).…

  12. 012The importer targets the Logseq file graph only, not the DB version's markdown export

    too, from their mirror plus db.sqlite.

  13. 013AI parity is a first-class requirement — undo and assets in MVP, live UI control designed for M2

    M2, not yet an implementation decision.

  14. 014Stay on Node for the server; Bun is a viable later swap, not a now decision

    The server keeps targeting Node (26.x, per ADR 001). Bun was evaluated and rejected for now, not on principle — the SqlDriver abstraction in packages/core (ADR 001/003) means the runtime is…

  15. 015Live UI control — a dedicated `/ui/live` socket, the existing command registry, consent by default-asymmetry

    Follows from ADR 013's forward-looking item. Full survey and rationale: docs/research/09-live-ui-control.md.

  16. 016Desktop app — Tauri, pointed at the local server

    The desktop app is a Tauri 2 shell. Electron is not used. - v1 loads http://127.0.0.1:6100 directly — the running nooklet serve — rather than bundling the web assets. The window is a native macOS…

  17. 017Page-level tags, and how a journal becomes `#Journal`

    A block becomes a task by carrying a marker, and ADR-less precedent (see apply-ops.ts's rebuildRefRows) now has every marked block emit a derived tag ref to the Task page. That works because refs…

  18. 018Journal pages are stored by ISO date; the title format is a setting

    A journal page has three names at once today:

  19. 019Templates are core; the journal template is a property of the graph

    Logseq's templates are three things: a block with template:: name is a template; /template inserts a copy of its subtree at the caret, with <% today %>-style tokens expanded; and…

  20. 020Block/page refactors and graph replace are server ops; a merge rewrites, aliases, and deletes

    Four of the most-asked-for things in Logseq's forum (research/13 §4.2, items 3 and 4) are structural edits that touch many blocks at once: turn a block into a page, move a block to a page, merge…

  21. 021Linked-reference filters are remembered per device, not as a page property

    M7 adds Logseq's linked-references filter (research/13 §4.2 item 5: "Filters for note body", 140 votes, "Sort linked references", 78): on a page's references panel, the set of other pages the…

  22. 022The trash never expires; history is the audit log; "restore this version" is a walk of undos

    item 10a's asset GC).

  23. 023The client plugin host compiles built-in client halves into the web build

    the owner yet.

  24. 024Pages exist once referenced

    Numbering: the brief named this file 023-…; 023-client-plugin-host.md already holds that number, so this is 024.

  25. 025A server hosts N graphs, routed by `/g/:graphId/`; a client remembers a list, not one slot

    Confirmed") and the "one graph per server" line in docs/spec/00-conventions.md's Graph definition.

  26. 026Ops read out of the server's log are applied in `seq` order, not re-sorted by HLC

    case with no arbiter; implements what docs/spec/sql-schema.md rule 26 already said for rebuild() ("replaying in seq order").

  27. 027The losing text of a same-block conflict becomes a sibling block, minted by the server

    research/03-sync.md §6.4's "add a props.conflictcopy with the loser".

  28. 028On the desktop, a "local graph" is a new graph on This Mac's bundled server

    docs/progress/desktop-local-graph.md. Bug: B-643.

  29. 029Pair devices with one-time codes behind an https page; `admin` gates device management

    branch, docs/progress/qr-pairing.md).

  30. 030Import Logseq DB-version graphs too, from the mirror plus `db.sqlite`

    ADR 012 scoped the importer to the classic file graph. The owner's real graph now lives in the Logseq DB version, and importing its Markdown Mirror as if it were a file graph lost data (B-711,…

  31. 031Import a Logseq graph from the app: chunked zip upload, server-side import, `admin` only

    docs/progress/in-app-import.md). Adds a way in to the importer of ADR 012 (file graphs) and ADR 030 (DB-version graphs); what is imported is theirs, unchanged.