Indstillingerne er ti faner med syvogtredive afsnit, og den eneste vej til et
bestemt af dem var at huske, hvilken fane det lå under. ⌘K kunne opgaver,
projekter og noter — alt det, man selv har skrevet, og intet af det,
programmet består af.
Ét indeks, to steder det bruges: et felt øverst i indstillingerne og paletten.
To lister over de samme afsnit ville være to steder at glemme et nyt et.
Overskrifterne står ikke i indekset. Hver post peger på den nøgle, afsnittets
egen overskrift allerede bruger, så et afsnit, der bliver omdøbt, følger med af
sig selv, og søgningen kan begge sprog uden at kende nogen af dem.
`words` er derimod skrevet i indekset, og det er med vilje: det er søgeord,
ingen får at se. Det danske afsnit hedder "Tælling af installationer", men det
ord, man kender det ved, er "beacon" — og på samme måde 2fa, imap, webcal,
webhook. De er de samme på begge sprog, hvilket er hele grunden til, at de
virker som søgeord.
Ankrene er nye id'er på afsnittene. Tre af dem stødte sammen med et felt inde i
afsnittet — to ens id'er gør <label for> tvetydig og getElementById tilfældig —
og hedder derfor to-faktor, kalenderfeed og caldav-server.
Springet dertil kan ikke overlades til browseren: siden hentes af ruteren, og
afsnittet findes først, når dens data er hjemme. Der prøves i to sekunder og
gives så op. Blinket bagefter er ikke pynt — man er landet midt i en side med
otte afsnit, man ikke selv rullede hen til.
Prøverne læser filerne frem for at klikke: halvdelen af afsnittene vises kun
under en betingelse, så en prøve, der ledte efter dem i en browser, ville melde
fejl på det, der med rette var skjult.
Og palettens pladsholder sagde "Søg i opgaver og projekter". Den har kunnet
noter længe, og kan nu også indstillinger.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Rapporteret som "AI gemmer ikke mine oplysninger". Anden gang det symptom
meldes, og en helt anden årsag end sidst — dengang delte scopes i user_settings
én række og slettede hinanden.
Udbyderen hedder "OpenAI-kompatibel (Ollama, LM Studio, …)" og feltet foreslår
selv http://localhost:11434/v1. safedial nægter præcis den slags adresser. Så
den ene udbyder, der findes for at køre sin egen model, kunne ikke sættes op,
og formularen foreslog en adresse, serveren ville afvise.
Serveren svarede 422 med en fejl på base_url. Formularen havde ingen plads til
at vise en fejl på det felt, og fordi fejlobjektet ikke var tomt, blev der
heller ikke vist en toast. Beskeden forsvandt, og det så ud, som om der ikke
skete noget ved at trykke Gem.
Tre rettelser:
- Private adresser tillades for admins. Muren står, fordi en server, der henter
en brugerangivet adresse, lader den, der angiver den, nå ting han ellers ikke
kan. En admin på en selvhostet instans ejer allerede værten og dens netværk,
så det giver ham intet nyt. For en inviteret bruger er den lukket som før.
Tilladelsen regnes ud pr. forespørgsel, ikke gemt ved siden af adressen — den,
der ikke længere er admin, skal ikke kunne nå indenfor på et gammelt flag.
- Tailnet er ikke "indenfor". Halvdelen virkede allerede ved et tilfælde: Gos
IsPrivate dækker RFC1918 og ikke det CGNAT-område, Tailscale låner, så 100.x
slap igennem uden at nogen havde besluttet det. IPv6-halvdelen gjorde ikke —
fd7a:115c:a1e0::/48 ligger inde i fc00::/7. Samme maskine var altså
tilgængelig eller ej alt efter adressefamilie. Begge områder er nu navngivet,
hvilket gør IPv4-halvdelen bevidst frem for heldig.
- Feltfejl kan ikke længere forsvinde. base_url og model har fået et sted at
vise en, og en fejl på et felt, der ikke har et, råbes op som en toast frem
for at blive tabt.
The account has had a `locale` since the beginning and it only chose which grammar
quick add read a line with — so picking English changed nothing anybody could see,
and the field said so honestly: "Sprog i hurtig tilføjelse". It says "Sprog" now,
because it means it.
Every component goes through `t()`. Danish is the source: a key missing from
English falls through to Danish rather than to the key, because a Danish sentence
in an English screen can be read and `settings.notifications.title` cannot.
`tag()` is the single place `Intl` is asked. Before, `'da-DK'` was written into a
dozen components, and that is the half of a translation that gets left behind — an
English interface still saying "24. aug." is exactly the shape of a job half done.
There is a smoke test that switches to English and refuses to see a Danish month
name in the calendar's heading.
Colour and theme names are keys now. `colors.js` keeps its ids untouched — they
are what the database stores, and `internal/store` reads them out of that file —
but `name` is `'color.tomato'`, resolved where it is drawn.
The Inbox keeps its name. It is a project row the server creates at signup, not a
string in the interface, and it can be renamed like any other project.
Four copies of "3 hours ago" became one, in `web/src/lib/when.js`. They had
already drifted: one said "aldrig logget ind", another "aldrig", and two stopped
at days where the others carried on to a date.
Routes stay Danish. `/indstillinger`, `/projekt`, `/uddelegeret` are bookmarks and
they are in every smoke test; translating them buys nothing and costs both.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was no /indstillinger route at all. The Gmail OAuth callback
redirects there and had been answering 404 since it was written.
Six pages rather than one long scroll, because the sections have nothing
to do with each other and somebody opening API tokens should not have to
walk past their own password to get there: the account and its second
factor, notifications, integrations, AI, tokens, and data. Everything
listed as having no interface in the handoff is reachable from one of
them — the calendar feed, the mail-to-task address, Gmail, the AI
provider, import, export, templates and the update notice.
Two decisions worth naming. The AI key field starts empty and says why:
a settings page that repopulates a password field is one that will
eventually leak a key into a screenshot, and an empty value means "leave
the stored one alone" rather than "clear it". And the form chrome lives
in the layout as :global rules, because scoped styles do not reach a
child route and six copies of the same twelve selectors is six places for
them to drift.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>