Portal 0.5 in the plan, mails scored as read, and a front door that shows

Portal 0.5's content, from the plan: `grants` (an approval that is
membership; refused without a mail sender, since the sign-in link goes
by mail), `deadlines` (portal's `after:`, only along a move the
machine has), and `public_states` with an `empty` line on public lists,
whose words now say which stages they show.

Onboarding is a mail too: a plan that invites or grants gets
`mail.invite` in site.yaml in its own language, and a reword that loses
{link} gets it back. `rate --repo` rates every mail, the invite
included, read as it arrives: LIX, and what happened / what now / who
wrote, each against a gutted twin. The mail words are guided by what
that found on uhhm (name the case by a short required field, sign with
the business, invite a reply rather than promise a reason).

The front door shows public lists instead of pointing at them: each
public bucket is a feature with its newest three records inline and a
link to the whole list. Its card is named for what it shows. The plan
reference gains 'What a portal site can do': the schema's options and
when each pays off, as guidance rather than rules.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Bendik Aagaard Lynghaug
2026-09-28 20:34:53 +02:00
co-authored by Claude Opus 5.5
parent 040a95ee07
commit d4d4c7cba3
4 changed files with 399 additions and 46 deletions
+38 -2
View File
@@ -40,8 +40,8 @@ does: # the front door's first card: what the business d
for: <need id> # role the business exists for and the next one it serves.
text: what it does for them, in the intake's own words
shows: # things a visitor may look at before asking for anything;
- id: kebab-case # each `resource` is portal's resource block, verbatim
shows: # what the site shows as it is: a live figure, the latest
- id: kebab-case # releases, what has been built; `resource` is portal's block
text: one line on what it is
resource: { public: true, source: { kind: url, url: https://... } }
@@ -98,6 +98,16 @@ needs:
# who hears when a case enters a stage: `submitter` (the form
# must then collect a required `email`) or one address. Tell the
# person on arrival and on every decision that is about them.
grants: { <stage>: <group> }
# a stage that makes the person a member of a group when their
# case arrives (an approval that IS membership); the form must
# then collect a required `name` and a required `email`
deadlines: { <stage>: { days: 30, to: <stage> } }
# a stage a clock moves on from when a case sits in it that long
# (a notice that expires, a hearing with no answer); only along
# a move the stages already allow
public_states: [<stage>] # with `public` in watched_by: the stages the public list shows
# (a noticeboard shows what is up, not what was taken down)
stages_text: { open: <words>, <stage>: <words> }
# each stage in the intake's language, where the ASCII id
# would read wrong to a person: "ikke_lost" is "ikke løst"
@@ -124,6 +134,32 @@ needs:
- { name: address, optional: true, private: true } # never on a public page
```
What a portal site can do - use what serves this business, leave the
rest. Each is one line in the plan, and the builder does the wiring:
- **Show, not only ask.** A bucket with `watched_by: [public]` becomes
a public list: its newest few records sit inline on the front door,
linked to the whole list, contact details pruned. What neighbours
reported and where it stands, which changes were approved, what is
on the noticeboard: a visitor who sees the place working trusts it,
and a person who reported something sees it being dealt with.
`public_states` shows only the stages worth showing.
- **Stay in dialogue.** `mails` tells the person at the stages that
are about them, and a reply to that mail comes back to the case as
a note. `moves_by: { <stage>: [submitter] }` gives them a move of
their own through their link: confirm, withdraw, say it is solved.
- **Let a decision do the work.** `grants` makes an approval into
membership; `deadlines` lets a stage end on its own (a notice that
expires, a request nobody took up); `invites_by` lets a desk bring a
person in without anyone touching an admin tool.
- **Make the choices real.** `groups` with labels, and a select with
`options_from: groups`, so a form offers exactly the committees or
teams that exist; `multiple: true` where a person can pick several.
- **Place things.** A `location` field puts a pin on a map (where the
hazard is, where the job is), when the site has maps.
- **Open with the business itself.** `does` says what it does for the
roles it exists for; `shows` puts its live content beside that.
Rules:
- One need per (kind of person, thing they want). Work inside the
business counts: a role recording or posting something is a need