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>
99 lines
4.8 KiB
Markdown
99 lines
4.8 KiB
Markdown
# 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](beacon.md). The edition is not in
|
|
the payload, so the count does not distinguish them.
|