iOS is the one client none of the mail autoconfiguration reaches: Apple
Mail reads neither Mozilla autoconfig nor Autodiscover for IMAP, so
adding an account means the device probing for ports - and a probe that
meets a filtered port waits out a TCP timeout and reports the account as
unverifiable, which the person reads as a wrong password.
A profile states the hosts and the ports instead, and carries mail,
calendars and contacts in one install. It invents none of them: site.yaml
names a PACC document - the standard defined to be fetched from the mail
domain itself - and every host, port and collection path comes from
there, so the server is described in one place and the profile follows it.
The document holds no address, username or password. iOS asks for those
at install, which means one profile serves everyone with an account and
it can be fetched by anyone: it says no more than PACC already says in
public. Identifiers are derived from the site and the payload rather than
drawn at random, because iOS replaces a profile whose ids it knows and
stacks a duplicate when they differ.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015GeH3y8fwTaTudaD88P57o
A kind: url resource whose JSON is a releases manifest - latest,
versions newest first with per-platform assets, channels such as a
package repo or TestFlight - is one card: the newest version, the file
for the visitor's own platform (read from the browser on hydration;
the server renders every file), the Linux package repo lines beside a
Linux file, TestFlight for iPhones and Macs, and the rest folded under
"All downloads". Decided by shape, as every resource is; written for
redoal's public releases at dl.redoal.com.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`limit: n` on a resource spec, applied after `jq`.
The releases card is what asked for it: a page that grows by one card
per release gets longer forever, and the cut a visitor should run is the
newest one — the rest are history, not an invitation. redoal's card had
every release on it.
After `jq` rather than before, for two reasons: it caps what the page
will actually render whatever the filter did, and content that reorders
with the filter can then take the top of its own ordering rather than
the top of the source's. A source yielding a single record has no length
and passes through untouched — turning one record into an empty list
would be worse than ignoring a limit that cannot apply. `limit: 0` on a
list does mean nothing, and the `empty` line speaks instead, which is a
coherent thing for content to ask for.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ByshXuDEzH8hXh5qp4VgGk
A session link top right on a site with sign-in, and on a page with
pictures the wordmark as its own bar over the strip. A list may be
narrowed to the signed-in person's own records ({user.email}, from the
session), and an attachment follows the list that shows its record.
Form controls are sized in em: the date picker's icon and the file
picker's button scale and take the field's colours.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GT37Z1Xtfd9pUuQtMTg6Yt
slideshow on a page: max_height in svh, slides with an image (a path
in the content repo or https), a caption laid over the picture's foot,
and optionally the picture's size so its place is held before it
loads. Swiper upgrades it; before that it is a row that scrolls.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GT37Z1Xtfd9pUuQtMTg6Yt
Any text a person reads may be a list, one entry per storyline, every
list the same length. A visitor is given one storyline for the whole
site and reads it on every page and in every mail about their case.
Records and events carry the storyline; GET /report gives counts per
bucket, storyline and state, and nothing else.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GT37Z1Xtfd9pUuQtMTg6Yt
responsible: { name, contact, group } says the address is a group's
inbox, not a person: no account or mail is made for it, and any
signed-in member of the group gets the suggest-a-change card. A
board's shared address was onboarded as a person before this.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GT37Z1Xtfd9pUuQtMTg6Yt
Three small things that together make a board work: a link on every
listed record to a page about it (ResourceSpec.link, {id} and {field}
in the address); a state mail whose `to` is a field of the record this
one belongs to ({notice.email}), with that record's fields in the
text, and a lint that the form carries the record field it reaches
through; and value: "{user.email}" filled from the session and hidden,
so someone the site knows is not asked who they are.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GT37Z1Xtfd9pUuQtMTg6Yt
The Suspense fallback on a question is the whole served page until
the question resolves - no header, no wordmark - so the word
"loading…" was the first and only thing a visitor or a crawler read
about the site. True, and not about them. It holds its space and
stays quiet now; `aria-busy` says what is happening to a reader that
would otherwise find an empty document. Same for the two resource
slots inside a page, which keep a line's worth of room.
Also gone: the `gitea_org_repos` resource kind, which no site ever
named. `gitea_starred` and `gitea_releases` stay.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Five things a building authority needed and portal could not say,
found by building one and replaying its cases through the engine.
The one that mattered: a submitter could only ever act on a record
nobody had touched. `access.rs` pinned a self_transition's from-state
to `open`, though the runtime had always passed the real state and
asked. So the applicant written to for a missing drawing could not
send it, and nobody could withdraw once anything had happened.
`self_transition` now takes `from: [state, ...]` and compiles one rule
per state; omitted it still means the initial state alone, which is
what every unsubscribe link already meant.
Then the four beside it. A record can belong to another record
(`type: record`, `of:`), with `where: { field, equals }` on a listing
as the other half, so an application's page lists the objections that
name it. A public view can be narrowed to the states worth showing,
so a register that is public by law is public about what was decided
and not about what was withdrawn. A state can grant a group, so an
approval that means membership needs no second step. And a state can
be moved by a clock (`after: { days, to }`, src/deadlines.rs) - held
to the same declared edge as a desk button, firing the same mail,
publishing the same event.
Also: the README is a README again, and everything that was crowding
it is in docs/ - architecture, content reference, state machines,
access, operations.
95 tests, up from 82.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>