2026-10-07 01:11:25 +02:00
|
|
|
# Two editions: verdande and urd
|
|
|
|
|
|
|
|
|
|
One program, built once, with two faces.
|
|
|
|
|
|
|
|
|
|
| | **verdande** | **urd** |
|
|
|
|
|
|---|---|---|
|
|
|
|
|
| What it is | Tasks, projects and notes | Notes |
|
|
|
|
|
| `VERDANDE_EDITION` | `full` (the default) | `notes` |
|
2026-10-07 02:03:40 +02:00
|
|
|
| Routes served | 200 | 137 |
|
2026-10-07 01:11:25 +02:00
|
|
|
| Mark | ᚹ | ᚢ |
|
|
|
|
|
|
|
|
|
|
Set `VERDANDE_EDITION=notes` and the instance is urd: the task routes are not
|
|
|
|
|
mounted, the sidebar has no way to them, search answers with notes only, and the
|
|
|
|
|
program calls itself urd in its tab, its wordmark and its web manifest.
|
|
|
|
|
|
|
|
|
|
## Why one codebase and not a fork
|
|
|
|
|
|
|
|
|
|
The two share everything that is hard: accounts, sessions, passkeys, sharing,
|
|
|
|
|
invitations, the realtime socket, search, the backup format, the migrations. Notes
|
|
|
|
|
are about a twentieth of the Go in this repository and reference no task type;
|
|
|
|
|
tasks reference no note type. A fork would have duplicated the other nineteen
|
|
|
|
|
twentieths in order to separate the one — and then every security fix would have
|
|
|
|
|
needed applying twice, by hand, forever.
|
|
|
|
|
|
|
|
|
|
So the split is a configuration value read in one place, and the thing that keeps
|
|
|
|
|
it honest is a test rather than a convention: `TestNotesEditionServesOnlyItsOwnRoutes`
|
2026-10-07 02:03:40 +02:00
|
|
|
walks both routers, compares them against a written-down list of the 63 withheld
|
2026-10-07 01:11:25 +02:00
|
|
|
routes, and fails in **both** directions — a route that appears in the notes
|
|
|
|
|
edition and a route that stops being withheld are both a failure.
|
|
|
|
|
|
|
|
|
|
## An unknown value refuses to start
|
|
|
|
|
|
|
|
|
|
`VERDANDE_EDITION=note`, `Notes` and `none` are all rejected at startup, by name,
|
|
|
|
|
rather than being treated as "not notes".
|
|
|
|
|
|
|
|
|
|
The silent direction is the dangerous one here. An operator who sets up an instance
|
|
|
|
|
for notes and mistypes the value would otherwise get every task route served on it,
|
|
|
|
|
with no error anywhere and no way to tell from the outside.
|
|
|
|
|
|
|
|
|
|
## What urd does not have
|
|
|
|
|
|
2026-10-07 02:03:40 +02:00
|
|
|
The 63 withheld routes:
|
|
|
|
|
|
|
|
|
|
| | what |
|
|
|
|
|
|---|---|
|
|
|
|
|
| 29 | tasks, sub-tasks, labels, saved filters, reminders, the upcoming and delegated views |
|
|
|
|
|
| 8 | the CalDAV server — every route in it reads or writes a VTODO |
|
|
|
|
|
| 1 | `/.well-known/caldav`, which is a claim that a CalDAV server is here |
|
|
|
|
|
| 1 | the ICS calendar feed, plus 2 for the token it is reached with |
|
|
|
|
|
| 4 | the task exports: `tasks.ics`, `projects.zip`, and a project's `.csv` and `.ics` |
|
|
|
|
|
| 2 | the Todoist and CSV imports, which create tasks |
|
|
|
|
|
| 4 | project templates — a template is a project plus sections plus tasks |
|
|
|
|
|
| 5 | sections, which group tasks: `section_id` is a column on `tasks` and not on `notes` |
|
|
|
|
|
| 6 | the AI task features: split, tidy the inbox, and the day's plan |
|
|
|
|
|
| 1 | the inbox hook, which pushes a line of text in as a task |
|
|
|
|
|
|
|
|
|
|
Plus five of the nine MCP tools — `search_tasks`, `create_task`, `update_task`,
|
|
|
|
|
`complete_task` and `add_comment`. The endpoint is still there; the tool list is
|
|
|
|
|
shorter. A tool a model can call is a surface, and one that answers "there are no
|
|
|
|
|
tasks" is worse than one that does not exist: the model tries it, gets an empty
|
|
|
|
|
answer, and tells the person there is nothing to do.
|
|
|
|
|
|
|
|
|
|
Everything else is the same program:
|
2026-10-07 01:11:25 +02:00
|
|
|
|
|
|
|
|
- accounts, invitations, passkeys and two-factor
|
|
|
|
|
- projects — which in urd are what a note belongs to
|
|
|
|
|
- per-note sharing with other people, independent of projects
|
|
|
|
|
- search, over notes
|
|
|
|
|
- export, of notes
|
2026-10-07 02:03:40 +02:00
|
|
|
- the mail, calendar and MCP surfaces, minus their task routes and task tools
|
|
|
|
|
- AI settings, `ask`, and the note actions
|
|
|
|
|
|
|
|
|
|
!!! warning "Still being decided"
|
|
|
|
|
Four connectors are reachable in urd today and have not been settled either
|
|
|
|
|
way, because each has a plausible notes reading and withholding them would be
|
|
|
|
|
deciding what this product is rather than closing a hole: the mail-in address,
|
|
|
|
|
IMAP mailboxes, Gmail, and Google Calendar. They currently file what they
|
|
|
|
|
receive as **tasks**, so in urd they connect and then have nowhere to put
|
|
|
|
|
anything. The same applies to one handler rather than a route:
|
|
|
|
|
`POST /api/v1/ai/notes/{noteID}/actions/apply` creates tasks from a note.
|
2026-10-07 01:11:25 +02:00
|
|
|
|
|
|
|
|
## The name
|
|
|
|
|
|
|
|
|
|
Verðandi and Urðr are two of the three Norns in Völuspá, and the poem has them
|
|
|
|
|
cutting marks into wood — the act of writing something down. Verdande is what is
|
|
|
|
|
becoming; Urd is what has become and was kept.
|
|
|
|
|
|
|
|
|
|
An instance started without the task routes is a different product to the person
|
|
|
|
|
using it, so it says a different name. That name is read from the configuration in
|
|
|
|
|
one place (`Config.ProductName`), which is why there is no build-time flag and no
|
|
|
|
|
second frontend: the frontend is byte-identical in both editions, and the server
|
|
|
|
|
renames the two files a browser reads before any JavaScript runs — the HTML shell's
|
|
|
|
|
title and the web manifest.
|
|
|
|
|
|
|
|
|
|
!!! note "The beacon counts them separately"
|
|
|
|
|
Both editions report the same two values once a day — instance id and version
|
|
|
|
|
— and nothing else; see [The install count](beacon.md). The edition is not in
|
|
|
|
|
the payload, so the count does not distinguish them.
|