6 Commits
Author SHA1 Message Date
Claude 1836e673e7 fix(a11y): name account controls per account, stop hardcoding white on accent
A-04: every account rendered a folder toggle labelled "Toggle account folders",
so three accounts gave three controls a screen reader reads identically. The
address is in the label now, and the account link is named explicitly rather
than by a `truncate` span that CSS can cut mid-address.

A-07 (second half): computing --rgb-accent-ink by luminance achieves nothing
while 63 call sites paint `text-white` on `bg-accent` directly. They now use
text-accent-ink and inherit the measured choice. The two left behind are white
over a black gradient and over a danger fill, which are not the accent.

H-05: the search box kept a stale query when a folder was clicked — the input
survives the Turbo visit and a folder page has no `q` to overwrite it with, so
the box claimed a filter the list below it had not applied. It now tracks `?q=`
in the address bar, which is also right on the way back in.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 17:20:54 +02:00
Paul Lützner e5a591433e Merge settings-polish into backup-users for pre-merge verification 2026-08-09 14:04:21 +02:00
Claude Fable 5 03a0d6b877 A config backup knows its own operator
Three changes to the config-backup subsystem, made together because they
land on the same two pages and the same document.

**Users, and everything they configured, join the backup.** Restoring onto
a fresh install used to end at "this installation still has no users —
create the administrator account to finish", which is a backup of a server
rather than of an installation. Document version 2 adds a `users` section,
keyed by email because that is what an import matches on. Per person: the
password hash, the TOTP secret and its recovery codes, app passwords, mail
accounts with their IMAP/SMTP and OAuth credentials, aliases, per-user
integrations with their secrets, filters, labels, calendars and the share
links and booking pages whose token digests keep published URLs working.

Everything encrypted at rest leaves decrypted inside the envelope and is
re-encrypted under the target's key on the way in, exactly as the database
section already did — that is the only way a credential survives a move to
a host with a different APP_ENCRYPTION_KEY. Deliberately absent: trusted
devices and push subscriptions (grants to one browser or one phone, and a
trusted device is a skipped second factor), label bindings and sync state
(re-derived, or about a host that is not this one), avatars and background
images (filenames in a volume this file does not carry), and mail, calendar
entries and contacts, which have not moved an inch.

Import policy: a user this install does not have is created whole; a user
it does have is left completely alone, whole. Skipping is the design and
not a caution — a backup is a photograph of a moment that has passed, and
applying a stale one to a live account resets today's password to
February's with no undo and nobody told. Soft-deleted users count as
present. The ids inside a filter, a calendar or a booking page name rows in
the *source* database, so the restore builds a map as it creates rows and
rewrites every reference through it; a reference to something the file did
not carry is dropped rather than guessed at.

The /install restore page now has two endings and asks the guard which one
it is in: sign in as somebody the file restored, naming the administrator,
or — for a document that carried no users — create the administrator as
before.

**MAILER_DSN and MESSENGER_TRANSPORT_DSN leave the export.** Machine-local
deployment choices that compose supplies a default for, in DATABASE_URL's
category: the target's relay and queue are the ones that match the
containers running beside it. Old backups carrying them classify as
External and stay inert.

**The restore result page stops listing non-tasks.** A restored value
already identical to the live one is dropped from "These are yours to do"
and counted in one muted line; the APP_ENCRYPTION_KEY block becomes a
collapsed disclosure that still carries the line the one operator who needs
it came for; and when nothing is actionable the page says so and puts the
way out first.

Two robustness fixes fell out: planning no longer hydrates every user to
build a comparison (per-email now), and neither the existence check nor the
comparison can 500 a review page on an install whose encryption key was
rotated without re-encrypting.

No schema changes — the backup is a file format, not tables.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-09 14:02:15 +02:00
Claude 7c5a2031fa Make the file picker's trigger look like a button
Tailwind v4's preflight resets ::file-selector-button the same way it
resets everything else — transparent, unpadded, borderless, no radius —
so "Choose file" rendered as a phrase in a line of body text and people
read past it looking for the control.

The three visible file inputs did each carry `file:` utilities, in three
different looks: the ICS import had a real border, while the /install
restore and the admin backup import had `file:bg-raised file:border-0`,
which is a 4%-opacity tint and no edge. That is why this came in as
"unstyled" rather than as "inconsistent".

Styled once in the base layer instead, on the same reasoning the
`cursor: pointer` rule above it already gives: it reaches every file input
there will ever be, rather than the ones that remembered the utilities.
The per-template stacks are removed, so there is one definition. Built
from the --rgb-*/--*-alpha tokens rather than fixed colours, so all six
themes and both light and dark follow without a second rule.

The secondary control's look, not the accent one: picking a file is a step
towards the button the form is actually asking for, and two accent buttons
in one form is two primary actions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-09 13:31:40 +02:00
Paul LütznerandClaude Fable 5 1adac1fe40 Carry an instance's configuration in one password-sealed file
Export from the admin area: env secrets, the JWT keypair and the
database-held provider credentials - FCM included, decrypted inside the
envelope because the restore target holds a different encryption key -
sealed with Argon2id and secretbox at parameters a Raspberry Pi can
afford, in a documented format a php one-liner can open without plMail.

Import shows its plan before touching anything: what it can write is
written and re-encrypted under this instance's key, what it cannot -
process environment, the Postgres role's own password - is rendered as
the exact lines to place by hand. The install wizard offers the same
restore before the first account exists, behind the same guard that
closes /install forever afterwards.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 19:12:21 +02:00
Paul Lützner 122006460d onboarding. this is a lot 2026-07-30 16:40:31 +02:00