NENO Logo

NENO User Manual

Hello and welcome to the NENO user manual. Thank you for your interest in NENO. We hope that this manual provides you with all you need to become a productive user.

What is NENO?

NENO Editor view
NENO Editor view

NENO is a powerful note-taking app that helps you create and manage your personal knowledge garden. A personal knowledge garden is a graph of notes linked together. Here is a nice introduction to personal knowledge graphs by Dan McCreary. With NENO, you retain full control over your data because you decide where it is stored: On your device, on a cloud storage of your choice, or even on a server under your control.
NENO is open-source software which means it is completely free to use.

NENO Editor view in dark mode
NENO Editor view in dark mode

Design Principles

It is important to understand why NENO is designed the way it is. So let's consider our 4 core design principles.

Freedom to exit

NENO and its data format are not only open-source, they are designed for credible exit as they follow the file over app paradigm:

File over app is a philosophy: if you want to create digital artifacts that last, they must be files you can control, in formats that are easy to retrieve and read. Use tools that give you this freedom.

NENO's exchange format is not one big JSON blob which might have an open specification but is hard to be used with another app. NENO's data format is a bunch of human-readable plain text files which can be opened and edited with any simple text editor. Even if you decide to stop using NENO, you can still access your notes and use them with another app.

We think that credible exit is an essential factor for serious tools for thought, because we think that a person's knowledge is so precious that there should not even be the slightest possibility for it to get stuck or even get lost in a proprietary app or hard-to-parse data format.

Local-first

Local-first means that you own your data, in spite of the cloud. We want to give ownership and agency to our users. We want to give them the freedom to store and use their data in any way they want. Another way to put it is one of the three inverted key relationships that define today's computing, proposed by Bernhard Seefeld: Services come to the data instead of data going to services.

Bottom-up organization

NENO prominently shows backlinks of notes to enable serendipity and a bottom-up organization of ideas. Tyler Angert sums this up pretty well:

Anytime you link to something, both ends of the link should have references to it. This means that organization of pages and ideas is bottom up. You don’t have to create folders or categories ahead of time; the relationships between things define their organization. The idea of things being in one place becomes antiquated. You shouldn’t have to spend extra time deciding what folder something should live in when it’s related 10 other things.

This also means that there are no pre-defined categories like "book", "project", or "person" in NENO (or categories like the ones used in Tiago Forte's PARA model, for that matter). However you can create any category you need yourself by creating a note for that category and linking to that note from all notes that belong to this category.

Modeless markup

NENO does not use Markdown or different editing modes or views. Why? Gordon Brander sums it up nicely:

Markup like [HTML's or Markdown's link syntax] implies that apps should have a distinction between an edit-mode meant for writing, and a view-mode meant for reading. This creates indirection. Editing becomes something abstract. You have to hold in your head the difference between the symbolic markup and the rendered text, and imagine the rendered text while editing. This kind of indirection might be ok for an audience-centric publishing tool, but feels like a lot of cognitive overhead for taking notes.

Instead of offering several modes or a WYSIWYG editor with rich markup features that require a specialized editor, we want to keep it simple and provide a single-mode editor, and a text format with minimal markup features, optimized for efficient note-taking without the need for eyeball parsing.

Getting started

To get started with NENO, you do not need to create an account or install any software. NENO is a web app that runs in your browser.

Open the app.

Optional installation

Even though you don't need to install NENO, doing so has some advantages. Click on the install button in the address bar of your browser to install NENO as a PWA (Progressive Web App). This way, you can start NENO from your device like any other app.

Installation dialog for NENO in the browser
Installation dialog for NENO in the browser
NENO app icon in macOS dock
NENO app icon in macOS dock

Opening a folder

When you open NENO for the first time, you will be asked to select a folder where your notes will be stored. NENO stores all data in a folder of your choice on your device. If you want, you could also select a cloud storage folder so that your notes are synchronized to all your devices. You could also use a Git repository to manage your notes. That way, you also get versioning for free. Even though NENO is a web app, it does not send any user data to a web server.

Beware that yor web browser has to support a feature called "File System Access API". At the time of writing, the desktop versions of Chrome, Edge, Opera, and Safari do support it. Mobile browser versions are not supported yet. We recommend to use a Chromium-based browser (Chrome, Opera, Edge, etc.).

Taking notes

Taking notes in NENO is quite straight-forward. Just start writing your note in the editor and save it, either via the save button or via CTRL/CMD + S. You will see that the note list on the left now contains your first note:

My first note
My first note

The note is now stored in the selected folder and you can open it with any text editor.

Notes are written in Subtext, a format specifically designed for note-taking. Subtext documents are easily composable as Subtext is a block-based plain-text format with minimal markup syntax. Here's a guide to get you started with Subtext.

The features of Subtext
The features of Subtext

Linking other notes

You can link to other notes either via [[Wikilinks]] or /slashlinks. When using slashlinks, the editor will show a transclusion of the linked note.
When you create another note, you can add a link to the first note by clicking on the "Link to this note" button with the chain icon next to the list entry of that note:

Link to another note
Link to another note

Since NENO also displays the backlinks of a note, you might want to create notes for tags. For example, create a note called "quotes", and create another note with the following text:

> If you try and take a cat apart to see how it works, the first thing you have on your hands is a non-working cat. - Douglas Adams
[[quotes]]

If you now want to see all the quotes in your note graph, open the "quotes" note and look at the backlinks.

Slugs

Every note that you create is assigned a slug, which is a unique key to identify and find the note. The slug is also used as a note's filename. The slug is either generated based on the note's content, or you can manually set it. When referencing a note in a Wikilink, the link text is normalized to a slug. E.g. when writing the Wikilink [[My Favorite Note (probably)]], the note with the slug my-favorite-note-probably from the file my-favorite-note-probably.subtext will be linked. The same note will be linked when using these Wikilinks:

When using slashlinks, you have to use the unmodified slug: /my-favorite-note-probably

Updating slugs and references

Slugs of already saved notes can be updated any time. When editing the slug of an already saved note in the slug input, a toggle with the label "Update references" is displayed. When you save the new slug with the toggle activated, all wikilinks and slashlinks throughout the whole graph, that reference the current note, will also be updated.

Editing a slug of an already saved note will show the "Update
    references" toggle.
Editing a slug of an already saved note will show the "Update references" toggle.

If the title of the current note can be normalized to the new note slug, Wikilink references will be assigned the title as link text.

Example:
Let's change the slug of a note from old-note to new-note with the "Update references" toggle activated.
All slashlinks will change accordingly from /old-note to /new-note.
All wikilinks will change from Old note to New note when the title of the new note is "New note".
All wikilinks will change from Old note to new-note when the title of the new note is "Old note".

Be aware that the state of the toggle is saved for future slug edits.

Aliases

You can add aliases to a note. Aliases are alternative slugs that can be used to find the note. Click on the plus icon in the right corner of the slug bar to create a note alias, and then enter the alias in the new alias field.

A note with several aliases
A note with several aliases

Aliases are a useful feature when writing notes in multiple languages. For example, you can create a note with the slug "entropy" and assign it the German word "entropie" as note alias. Or you could add the alias "complex" to the note "complexity" and reference that note in a sentence like This algorithm is [[complex]].

Pins

You can pin notes to the topbar by clicking on the pin icon. Click it again to unpin the note.

Pinned notes on the top bar
Pinned notes on the top bar

Drag and drop pins within the title bar to move them to another position. Drag and drop pins to the editor section to insert a wikilink to the pinned note.

Search

By default, entering a value in the search input will search for note titles that include all the words given. By entering a token prefix followed by a : before the actual search string, you can change that behavior.

Token prefixes

exact: - Search for exact titles
Only notes whose title is an exact match are displayed.

Search for exact matches
Search for exact matches

duplicates:url - Show notes with same URLs When you have a lot of notes, chances are that the same URL is included in several notes. This search query helps you find those notes.

duplicates:title - Show notes with non-unique titles

ft: - Full-text search Perform a full-text search on all notes by typing `ft:` followed by the search query into the search box. The full-text search is case-insensitive.

has-block: - Search for notes that contain a specific block type. For example has-block:list. The following block types are supported:

has-media: - Search for notes that contain a specific media type. For example has-media:audio. The following media types are supported:

has-url: - Search for notes that contain a specific url For example has-url:https://example.com

$[KEY]: - Search for notes with key-value pair with the key [KEY]. For example $check-back: will find all notes that have a key-value pair with the key check-back.

$[KEY]:[VALUE] - Search for notes with key-value pair with the key [KEY] and the value [VALUE]. For example $check-back:2040-01-01 will find all notes that have a key-value pair with the key check-back and a value that includes 2040-01-01.

Search presets

You can save every search query as a preset by opening the "Search presets" view by clicking the button next to the search input field. You can also quickly query saved presets.

Search presets
Search presets

Keyboard Shortcuts

NENO has a few keyboard shortcuts to make your note-taking experience more efficient.

When the search input is focused you can select results with / and open the selected note with Enter.

Working with files

You can import any file into NENO and reference it in multiple notes, either via drag and drop, or by clicking on the "Upload file" button in the editor view. When importing a file, a slug will be created based on the file's name, by convention starting with the prefix files/, e.g. files/image.png. A slashlink to the file and a transclusion of the file is automatically added to the currently active note. If applicable, the transclusion contains a preview (images, plain text) or a player for the file (video, audio).

Note with a video
Note with a video

In the files view, you get an overview of all files in your notes folder. You can also search for files whose slugs that start with a specific string by entering that string into the search input, e. g. files/. If you want to find all files that end with a specific string, type ends-with: into the search input field, followed by the desired string, e.g. ends-with:.jpg.

Single file view

In the single file view, you can see all notes that reference the file. You can also create a new note that references the file.

Single file view
Single file view

Stats

It might be interesting to you to see some stats on your graph or see how many components it has. Use the "Stats" view for this, which you can find in the app menu.

Scripting

Scripting is probably the most powerful (and also most dangerous) feature of NENO as it allows the programmatic manipulation of your knowledge graph, using the full flexibility of a JavaScript environment. Use with caution and be sure to backup your data beforehand.

The possibilities are endless but here are some example use cases:

Creating a script

Go to "Files" view. Scroll to the section "Create script" at the bottom, enter the name of the script and click on "Create". You should now be able too see the new script in the files list. Click on it and then click on "Open in script editor".

Variables

The following variables are defined in the JavaScript environment:

notesProvider

A collection of functions for accessing and manipulating the currently loaded graph.

Type: NotesProvider

storageProvider

A collection of functions for reading from and writing to the file system. Operations are limited to the graph folder. Use this interface if you need a low-level way of manipulating the graph or if you want to save arbitrary files.

Type: StorageProvider

graph

A representation of the currently loaded graph, including all notes, aliases, and some metadata.

Type: GraphObject

println

A function to add a line to the output.

Type: (val: string): void

Example scripts

List all aliases and their canonical slugs

println(
    "Graph contains "
    + graph.aliases.size
    + " aliase(s):"
);

for (const [alias, slug] of graph.aliases) {
    println(
        alias.padEnd(25, " ") + "=> " + slug);
}

List events

All events (links in the form of [[YYYY-MM-DD]], e.g. [[2024-06-01]]) will be listed here, ordered by date and grouped by month.

const SHOW_NOTE_CREATED_EVENTS = false;
const START_YEAR = 2024;
const START_MONTH = 5;

const MONTH_NAMES = [
  "January",
  "February",
  "March",
  "April",
  "May",
  "June",
  "July",
  "August",
  "September",
  "October",
  "November",
  "December",
];

function createEvent(note, date, type) {
  const month = parseInt(date.substring(5, 7));
  const year = parseInt(date.substring(0, 4));
  return {
    title: getNoteTitle(note.content),
    date,
    month,
    year,
    type,
  };
}

function getEventsOfNote(slug, note) {
  const events = [];

  if (SHOW_NOTE_CREATED_EVENTS) {
    const creationDate = note.meta.createdAt
      .substring(0, 10);
    events.push(
      createEvent(note, creationDate, "NOTE_CREATED"),
    );
  }

  if (slug.match(/\d{4}-\d{2}-\d{2}/)) {
    const date = slug.match(/\d{4}-\d{2}-\d{2}/)[0];
    const type = "MENTIONED_IN_SLUG";
    events.push(createEvent(note, date, type));
  }
  const dateMatches = note.content.matchAll(
    /\[\[(\d{4}-\d{2}-\d{2})\]\]/g,
  );
  for (const match of dateMatches) {
    const date = match[1];
    const type = "MENTIONED_IN_CONTENT";
    events.push(createEvent(note, date, type));
  }

  return events;
}

const table = Array.from(graph.notes.entries())
  .map(([slug, note]) => [...getEventsOfNote(slug, note)])
  .flat()
  .filter((event) => {
    return event.year > START_YEAR
      || (
        event.year === START_YEAR
        && event.month >= START_MONTH
      );
  })
  .toSorted((a,b) => {
    if (a.date > b.date) return 1;
    if (a.date < b.date) return -1;
    return 0;
  })
  .reduce((accumulator, thisEvent, i, events) => {
    const previousEvent = events[i-1];
    if ((!previousEvent) || previousEvent.month !== thisEvent.month) {
      if (previousEvent) {
        accumulator += "\n";
      }
      accumulator += MONTH_NAMES[thisEvent.month - 1] + " " + thisEvent.year + "\n";
    }
    return accumulator += thisEvent.date
      + " | " + thisEvent.title.padEnd(35, " ")
      + " | " + thisEvent.type
      + "\n";
  }, "");

println(table);

List all invalid links

Prints a list of red links (non-existing slugs that are linked somewhere), and how often and in which notes they are referenced.

const redLinks = new Map();

const allExistingSlugs = new Set([
  ...graph.notes.keys(),
  ...graph.aliases.keys(),
  ...graph.files.keys(),
]);

for (const [slug, note] of graph.notes.entries()) {
  const spans = getAllInlineSpans(graph.indexes.blocks.get(slug));
  const linkTargets = getSlugsFromInlineText(spans);
  for (const linkTarget of linkTargets) {
    if (!allExistingSlugs.has(linkTarget)) {
      if (redLinks.has(linkTarget)) {
        redLinks.get(linkTarget).add(slug);
      } else {
        redLinks.set(linkTarget, new Set([slug]));
      }
    }
  }
}

Array.from(redLinks.entries())
  .toSorted((a, b) => b[1].size - a[1].size)
  .forEach(([target, refs]) => {
    println(
      target.padEnd(20, " ")
      + " | " + refs.size
      + " | " + Array.from(refs)
    );
  });
  

Acknowledgements

NENO's primitives are inspired by the work of the Subconscious project.

Significant inspiration for the project comes from my work at the Niklas Luhmann Archive, from the people of the Subconscious Discord channel and from the work of Andy Matuschak.

NENO is built with the help of many open-source projects. Here are the most important ones:

License

NENO is licensed under the Apache License, Version 2.0.