Features

A tour of what nooklet does today, with partial and missing pieces marked.

This page describes what the code does as of October 2026. Items marked partial work with gaps; items marked being improved have open work against them right now.

Outliner#

  • Every bullet is one block of markdown. Blocks nest; Tab and Shift+Tab indent and outdent.
  • Enter splits a block at the cursor, Backspace at the start merges into the previous block, and Up/Down move between blocks keeping the column.
  • Fold a block (Cmd/Ctrl+Up and Down), zoom into it (Cmd/Ctrl+.), move it (Alt+Up and Down), or drag it by its bullet.
  • Escape selects the block; Shift+Up and Down extend the selection. Copy, cut, delete, indent and move work on a selection.
  • Blocks hold headings, numbered lists, tables, code fences (highlighted, loaded on demand), KaTeX math, images and other pasted files.
  • Undo and redo cross blocks and survive sync: the editor records inverse ops.
  • Commands: Cmd/Ctrl+K opens a palette listing every command and page. Typing / in a block opens the slash menu. In-app help lists the bound keys. Rebinding keys in a settings screen is not built yet.

Journals#

  • The Journals view is a stream: today at the top, earlier non-empty days below.
  • A day becomes a page only when you write in it. Empty days never exist as pages.
  • A calendar opens any day.
  • Each day shows a "Scheduled and deadline" section: open tasks and dated blocks for that day, plus overdue tasks on today.
  • The journal title format is a setting. Pages are stored under ISO names (2026-09-07), and references written in other date formats resolve to the same day.
  • A block marked journal-template:: true becomes the starting content of each new day.
  • [[Page]], #tag, #[[two words]], ((block ref)) and {{embed}} of a page or block. A block reference renders the referenced block's text.
  • Aliases (alias::), and namespaces written as Projects/Aurora, with a tree of children on the namespace page.
  • A page exists once something links to it, and a page nothing links to and nobody wrote in goes away again.
  • Each page lists linked references grouped by source page, with filters and sort saved per device, plus unlinked mentions. "Link all" turns mentions into links.
  • Page and block properties (key:: value), page-level tags, page icons, and favorites.
  • Refactors: turn a block into a page, move a block to another page, merge two pages, rename a page (every link follows), and find-and-replace across the graph.
  • A graph view of pages and the links between them.
  • Being improved: references and the graph view come from the server. On a local-only graph they say they need a server, and offline they show an error with Retry.

Tasks#

  • A block is a task when its first word is a marker: TODO, DOING, DONE, WAITING, CANCELED, or Logseq's other workflow, LATER and NOW. Each state has its own glyph.
  • Cmd/Ctrl+Enter cycles the marker. The graph's workflow decides where a cycle starts: TODO → DOING → DONE or LATER → NOW → DONE. An imported Logseq graph keeps its :preferred-workflow; otherwise nooklet infers the workflow from the markers you use, and Settings can override it.
  • Priorities [#A], [#B], [#C].
  • scheduled:: and deadline:: dates with optional time, set from /scheduled and /deadline. repeat:: 1w or repeat:: 1w from done repeats a task. Completing a task stamps done::.
  • The Tasks view lists open tasks by page, with filters for state, tag, namespace and date window.
  • Being improved: on the phone there is no visible way to set DOING yet.

Queries#

A fenced code block with the language query runs a filter against your graph and renders the matching blocks, grouped by page:

```query
(TODO or DOING) [[Project X]] scheduled:<=today sort:deadline
```

The language covers markers, priorities, tags and links, page and namespace, journal date windows, date fields with relative values (-7d, +1m), text, properties, sort and limit. Queries run on the device, so they work offline. Logseq's Datalog {{query}} is not supported.

Templates#

A block with template:: name is a template. /template inserts a copy of its subtree at the cursor with fresh ids, and expands tokens such as <% today %>.

  • On the device first. Every replica has a full-text index (SQLite FTS5, diacritics folded: c finds č). Results appear within milliseconds, offline and on local-only graphs.
  • Semantic matches from the server. When the device has a server and the server has an embedding model, the app also asks the server for hybrid search (keyword plus vector similarity) and merges its extra hits below what you already see. A device never runs a model itself. If the server takes longer than 6 seconds, you keep the local results.
  • Semantic search is off until you configure an embedding model on the server. See Self-hosting.
  • The page switcher (Cmd/Ctrl+O) is fuzzy and accent-insensitive.
  • Over the API, related_find returns similar pages or blocks by embedding. The app has no "related" panel yet.

Trash and history#

  • Deleting a page or block moves it to the trash. The trash has no expiry. Restore from the Trash view.
  • Each page has a history view built from the audit log: who changed what (you, an agent's token label, sync), with undo of one change and "restore this version".

Import and export#

  • nooklet import <dir> reads a Logseq file graph: journals, pages, properties, front matter, id:: block ids (mapped to nooklet ids, with references rewritten), org-style SCHEDULED: and DEADLINE: lines, and config.edn's journal format and task workflow. The newer Logseq DB version's export is not a supported source.
  • Running import again skips pages that already exist; it does not update them.
  • The markdown mirror writes every page as it changes. nooklet export rewrites all of it.

Plugins#

  • A plugin is a directory with a manifest and an optional server half and client half. The server half can add operations (which also become HTTP routes and, if enabled, MCP tools), hooks, scheduled jobs and settings; the client half can add commands, slash items, code-block renderers and status items.
  • Partial: the app loads client halves only for the built-in plugins, which are compiled into the web build. A plugin you drop into a graph's plugins/ directory runs its server half; its client half is not loaded yet.
  • Plugins are trusted code: a server half runs inside the server process with full access. Install only plugins you have read.
  • Built-in plugins: mermaid (diagrams in code fences), word-count (an MCP tool and a command), daily-summary (off by default).
  • Manage them with nooklet plugin list|enable|disable|reload.

Apps and devices#

  • Web client. The server serves it. It works as an installable PWA and keeps its replica in the browser's origin-private file system (OPFS). It needs a secure context: HTTPS, or localhost.
  • macOS desktop app. A Tauri window. In local mode it starts its own bundled server on port 6100 with the data directory ~/.nooklet/default (or $NOOKLET_DATA). It can also point at a server elsewhere: menu → Switch Server…. Being improved: adding a local graph from the graph switcher in the desktop app.
  • iOS app. A Capacitor shell around the same client. It can run "Just this device" with no server, or sync with one. You build and sign it yourself in Xcode today. Partial: tested on the iOS Simulator and one physical iPhone; phone layout issues are being fixed.
  • Multiple graphs. One server hosts any number of graphs, each under /g/<graph-id>/ with its own database, mirror, assets and tokens. A device keeps a list of graphs and switches between them; a local-only graph can be promoted to a new graph on a server. Two populated graphs are never merged.

Agents#

An MCP server and an HTTP API with OpenAPI, covering reading, writing, refactors, search, trash, history and undo, plus optional live control of an open window. See For AI agents.

Known gaps#

The full list is docs/BUGS.md. Notable open items as of October 2026: a synced block can render with invisible text until you open it; the slash menu does not open on the phone; a same-block edit conflict shows the losing text as a conflict_copy:: property (being changed to readable blocks); page icons have no emoji picker yet.