Prøven, der skal holde udgaverne adskilt, stod grøn gennem to merges, mens
34 opgaveruter blev betjent af noter-udgaven. Den fejlede ikke: den
sammenligner routeren med en nedskreven liste, og listen var skrevet ud
fra de ruter, nogen havde tænkt på. Den er en DRIFT-kontrol, og den kan
ikke være en fuldstændighedskontrol. Det står nu i prøven selv.
Lukket: CalDAV-træet (8 ruter, alle læser eller skriver en VTODO),
.well-known/caldav (en påstand om at der er en CalDAV-server), ICS-feedet
og de to ruter til dets token, de fire opgave-eksporter, Todoist- og
CSV-importen, skabelonerne, sektionerne, de seks AI-opgaveruter og
indbakke-krogen. Plus fem af ni MCP-værktøjer — endepunktet er der
stadig, værktøjslisten er kortere.
Tre blev afgjort ved at læse frem for ved navnet: en skabelon laver
opgaver (CreateProjectFromTemplate), `section_id` står på `tasks` og
findes ikke på `notes`, og add_comment hænger på en opgave.
Men den dyre halvdel var fladen. En vandring gennem hver side, der kan
nås i noter-udgaven, med konsollen og svarene aflæst, fandt SYV brud —
fire af dem fra den første port:
sidebjælken GET /filters 404 ved hver navigation, ædt af .catch()
sidebjælken GET /labels 404 ved hver navigation, ædt af .catch()
notifikationer GET /ai/plan 404, stille
notesbogens side GET /tasks kastede → status = 'failed'
notesbogens side GET /sections kastede → samme fejlskærm
indstillinger GET /templates toast med fejlbesked på skærmen
integrationer GET /feed 404, stille
Den fjerde er den, der har kostet noget: /projekt/{id} er notesbogens
side, og kaldene samles i et Promise.all, så én 404 tog hele siden med.
Den har vist en fejlskærm frem for noter siden porten blev sat. Ingen
prøve åbnede en notesbog i den udgave, og .catch(() => {}) sørgede for,
at resten ikke sagde noget. "Fladen følger udgaven" var sandt om menuen
og falsk om hentningerne.
Vagten for opgaver står i butikken frem for ved kaldestederne: der var
tre, og den fjerde blev glemt — hvilket er præcis sådan, den slap
igennem.
Og rettelsen lavede selv en fejl, som vandringen fangede: da opgavekaldet
holdt op med at fejle, kom en uendelig effekt-løkke frem, som 404'eren
havde holdt låg på. En ubetinget tilskrivning til app.tasks får
projektsidens effekt til at kalde tilbage. 404'eren var låget på en
løkke, ikke bare støj.
Også: -timeout 20m i begge CI-workflows. internal/httpapi tog 664s her og
498s i CI på #95, mod Go's standardgrænse på 600 — og når den krydses, er
beskeden "panic: test timed out after 10m0s" plus et goroutine-dump, som
læser som en deadlock i koden frem for som en suite, der blev for stor.
watchForTrouble udskilt til web/e2e/trouble.js, så udgave-suiten kan bruge
den samme.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
4.8 KiB
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 |
| Routes served | 200 | 137 |
| 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
walks both routers, compares them against a written-down list of the 63 withheld
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
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:
- 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
- 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.
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. The edition is not in the payload, so the count does not distinguish them.