Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Changelog

One line per change, grouped by the release that shipped it. Newest first. A release is a Cargo.toml bump plus a v<version> tag; the content repos pin the tag they run (see the end of this file).

0.6.0 (2026-09-30)

  • One binary. The lint (uhhm/iris) and the trainer (uhhm/portal-trainer) are commands of portal: portal serve (the default), portal lint, and portal plan, build, answer, rate, prefer, score, simulate, compare, report, generate. Same source, same version, so the site, the lint and the trainer never disagree about what content means; both histories are merged in. The release attaches the bare portal binary beside the tarball, so a content repo’s CI downloads it and runs portal lint. No more iris releases; the pins that exist keep working. The trainer’s plans/, intakes/, engines/, sectors/ and its reference prompt live here now; its runs/ stay in uhhm/portal-trainer. Docs gained “Training a site” and “The lint”.

0.5.11 (2026-09-30, tagged, never published)

The publish job failed at its lint step: iris, built against this tag, did not compile against the storylines change (the same two fixes that went into src/lint/ when the lint moved in). Nothing was attached to the tag; 0.6.0 is the first release that ships storylines.

  • Storylines (src/content/variants.rs, docs/storylines.md): 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 (the portal_storyline cookie, a year) and reads it on every page and in every mail about their case. What the machine reads - a field’s name, options, states, groups, paths - never varies. The lint refuses a list of another length. Records and answer events carry storyline, and GET /report gives counts per bucket, per storyline, per state, and nothing else.

0.5.10 (2026-09-30)

  • The lint lets an invite form collect a required phone in place of a required email: memberships know a person by number first.

0.5.9 (2026-09-30)

  • With memberships as the people backend and a Kanidm token still set, an invite makes the Kanidm account as well (no group, the one-time link in the mail): Kanidm makes people, portal makes members, and a site can switch to memberships before its sign-in provider is one everyone already has.

0.5.8 (2026-09-30)

  • Fixed: 0.5.7 asked every provider for the phoneNumber scope, and Kanidm denies a sign-in that asks for a scope the person does not hold, so nobody could sign in. The scope is asked for only with OIDC_EXTRA_SCOPES=phoneNumber (Vipps). 0.5.7 must not be deployed on a Kanidm site.

0.5.7 (2026-09-30)

  • Memberships: groups kept by portal itself (src/memberships.rs, docs/memberships.md). A membership is a case in the built-in portal_memberships bucket - invited by a desk, a grants, the seed or the responsible pass; active from the person’s first sign-in, when the provider’s subject, phone number or address matches; removed by a desk. A person is their phone number first (+4791234567, PHONE_COUNTRY for bare numbers), their address second, the provider’s subject third, and one record per group and person however it was keyed. At sign-in the active memberships join the groups claim, so Cedar sees the same parents as before. PEOPLE_BACKEND=memberships switches a site; unset, a KANIDM_API_TOKEN means Kanidm and no token means memberships. SEED_ADMIN_PHONE seeds by number. The sign-in asks for the phoneNumber scope and reads phone_number from userinfo.
  • The NATS access service answers for a person named by sub, phone or email with no groups listed: their memberships are looked up first.
  • who_for (the person a grants is about) takes navn as well as name, and a phone number in place of an address.
  • The invite mail for someone who needs no link says where to sign in.

0.5.6 (2026-09-30)

  • A group may hold a page’s responsibility: responsible: { name, contact, group: styret }. No account or mail for the inbox, and any signed-in member of the group gets the suggest-a-change card. Before this, a board’s shared address was onboarded as a person.

0.5.5 (2026-09-30)

  • One record answers another, from a list. link on a resource puts a link on every listed record to a page about it ({id}, {field}), a state mail’s to may be a field of the record this one belongs to ({notice.email}, and {notice.field} in the text), and value: "{user.email}" fills a field from the session and hides it, so a signed-in person is not asked who they are. Together they make a board: a notice is listed with “answer this”, the answer page’s form carries the notice’s id, and the mail goes to whoever posted it. The lint requires the record field the mail reaches through.
  • The way down the tree is the line at the foot of the page and nothing else. The arrow fixed at the left edge, which 0.5.3 added beside it, is gone: it followed the reader down every page to offer something the foot already does.

0.5.4 (2026-09-29)

  • Accounts the content mandates, made at start (src/seed.rs). Every person a page names as responsible gets a Kanidm account if they have none, and a mail in the site’s language saying they are listed as responsible for asking that question, with the link to sign in; RESPONSIBLE_GROUP puts them all in one group (people who already had an account are added without a mail). SEED_ADMIN_EMAIL (and SEED_ADMIN_NAME, SEED_GROUP) invites the first person into the site’s most privileged group - the group whose desk may invite into the most groups - so a site with its own Kanidm has someone to invite everyone else. Both happen once per address and group (portal_seed), side by side, and each tries again a minute apart, twenty times, while Kanidm comes up. The responsible are gone over again whenever the content changes (portal.content.reloaded, published once new content is in place), so a new page’s person is set up without a restart - and someone the content has stopped naming leaves RESPONSIBLE_GROUP in the same pass, if portal is who put them there.
  • An invite is a case that keeps itself current (portal_invites: open, expired, done). Signing in closes a person’s invites; a sweep every ten minutes moves one whose link ran out unused to expired, and takes the link off the record. From expired a desk may send it again: the move back to open makes a fresh link and mails it. Content offers that with { from: expired, to: open } on the list of invites. The invites portal makes itself at start are on file with the rest.
  • Fixed: an invite mail said the link worked “until” and then nothing. Kanidm 1.11 gives a link’s end as seconds since the epoch, and portal read only the older RFC 3339 string.
  • Fixed: a search by mail that Kanidm failed to answer (a 5xx) was read as “not permitted”, and the invite went on to match by username - which could give someone with an account under another name a second one. Only a refusal is read that way now.
  • A page’s responsible person, signed in, may suggest a change to it: a card at the foot of their own pages, checked on the server against the page’s responsible.contact, published on portal.edits.suggested, and mailed to the site’s inbox with them as reply-to. It is a case in the built-in portal_edit_suggestions bucket - open, discussed, done or declined - so a desk that lists the bucket takes it up and settles it, the person who made it is mailed at each move, and the card shows them what became of what they sent before. The session now carries the signed-in person’s email.
  • The mail to someone named responsible names the first three questions and counts the rest; on a site where one address answers for every page it used to list them all.
  • Fixed: an invite of someone new failed on a Kanidm set up with the onboarding account alone. Portal created the person, then set their address in a second call, and idm_people_on_boarding may create but not modify: 403. The address now goes in with the person.

0.5.3 (2026-09-29)

  • A way down the tree from every page: an arrow fixed at the left edge, half-way up, and the same link in words at the foot (“Tilbake til starten”, “Back to “What came in?””). It goes to the nearest page below the current one that the visitor may open, taking the path apart a segment at a time, and to the front door when none is; the front door has none. Content no longer needs a “back” alternative, and a visitor who went up a branch that turned out not to apply is never stranded.

0.5.2 (2026-09-28)

  • The rate limiter answered 500 to any request that arrived without X-Forwarded-For. It reads the caller from that header or X-Real-IP and falls back to the socket, and 0.5.0 served the app without connect info, so there was no socket to fall back to and no key to rate-limit by. Through Caddy every request carries the header and the site was fine; a health check on localhost got a 500, which is how the first deploy of 0.5.1 failed.

0.5.1 (2026-09-28)

  • A self_transition that names no from means the bucket’s own initial state, not the word open. 0.5.0 read the literal, which is only right for a bucket whose records start there: redoal’s watchers arrive at pending and confirm themselves into open, so the rule compiled as open -> open and the confirm link in the mail could never match. Wrong the same way before 0.5.0, and now visible because the lint checks the edge exists.

0.5.0 (2026-09-28)

  • Sessions live in NATS KV (src/sessions.rs), not in memory. Every restart used to sign out every desk on the site being deployed; they now survive it, in the JetStream that is already under everything else - no new service and no new store to run. The bucket’s max_age collects what nobody returns for, and a record’s own expiry is what answers a load.
  • Fixed: the JetStream integration test asserted a record’s projected state was one a desk move had never written to it, so it failed whenever it actually ran. test.yml has been red since 0.3.47 while publish.yml stayed green, which is how 0.4.0 shipped past it.
  • A file can come back out. GET /attachment/{bucket}/{record}/{field} streams what a type: file field stored, reading the object key out of the record rather than taking one from the caller - so a file is only reachable through a record somebody may see, and a key that leaks off a desk is a link to nothing. Before this, uploads were one-way: a desk showed the key as a string and there was no way to open it.
  • Rate limiting, per caller IP, outside everything else: a burst of 30 refilled one every two seconds (PORTAL_RATE_BURST, PORTAL_RATE_SECONDS; either 0 turns it off). A form-filling visitor never reaches it; a script does. There was none at all before.
  • /upload asks the policy, like every other way in. It asked nothing: any caller who could reach the host could put 20 MB in the bucket, as often as they liked, and a site whose forms are public is a site whose upload route is public. It now takes the question and alternative the file is being attached to, refuses a page that declares no type: file field or an alternative that is closed, and asks Submit on that page - the same question the submission itself will ask - before a byte is written. Latent until an instance had storage configured; the first one now does.
  • A submitter stays in dialogue with their case for as long as it runs. self_transition takes from: [state, ...], and the access policy compiles one rule per state named; it defaulted to the initial state alone and was written that way in the compiler, so an applicant written to for a missing drawing could not send it, and nobody could withdraw once anything had happened. Omitted, it still means the initial state alone.
  • A record can belong to another record. type: record with of: <bucket> stores the parent’s id, which arrives on the link rather than being typed; a submission naming a record that is not there is refused. The other half is where: { field, equals } on a Kv resource, so an application’s page can list the objections that name it.
  • A public view need not publish the whole bucket. states: [...] on a Kv resource shows only those, for one record as well as a listing - a register that is public by law is public about what was decided, not about what was withdrawn. The desk over the same bucket is a different spec and still sees all of it.
  • A state can grant a group: grants: <group> beside its event, so an approval that means membership does not also need somebody to remember to invite them afterwards. The lint requires the forms that fill such a bucket to collect a required name and email, because by the time a desk presses approve there is nobody left to ask.
  • A state can be moved by a clock: after: { days, to }, swept hourly, measured from the record’s last move (src/deadlines.rs). A deadline may only do what a person could have done - after.to is held to the same declared edge as a desk button - and the move fires the same mail and publishes the same event, stamped deadline.
  • Documentation moved out of the README into docs/: architecture, content reference, state machines, access, operations.
  • A page says nothing until it has something to say. The Suspense fallback on a question was the whole served page - no header, no wordmark - so the first and only thing a visitor or a crawler read was the word “loading…”, which is true and is not about them. It now holds the space and stays quiet, with aria-busy for a reader that would otherwise find an empty document.
  • Gone: the gitea_org_repos resource kind, which no site ever named. gitea_starred and gitea_releases stay.

0.4.0 (2026-09-24)

  • A field can be a place: type: location puts a pin on a map and stores {lat, lon, zoom}; a record holding one renders as a still map on its desk and in any resource card.
  • A question can be about a place: place: { lat, lon, zoom, name } is the counterpart to event: - event makes a page an occasion with a time, place makes it somewhere, draws the map under the question, and opens any location field on that page there instead of on the globe.
  • Maps are served from this origin. Portal fetches every tile and every still itself (src/maps.rs, MapLibre vendored), so a page with a map on it makes no third-party request, needs no consent banner, and the MAPBOX_TOKEN never leaves the server. Unset, the feature is simply off.
  • A reply to a case mail becomes a note on its record: whatever reads the mailbox publishes on portal.mail.received and portal appends the note, after checking the record exists and the sender is the address the record itself holds. A note never moves a case.

0.3.47 (2026-09-24)

  • iris: needs.yaml takes stage_words, each state in the site’s own words, carried into the simulation model for the trainer’s desk score. No change to portal itself.

0.3.46 (2026-09-23)

  • An invite first looks the email up in Kanidm (/v1/raw/search on mail, when the service account may read people: idm_people_pii_read) and only adds an existing account, under whatever username, to the group; no second account, no reset link.

0.3.45 (2026-09-23)

  • A mail stream that cannot be created at boot (subjects owned by an older stream) is a warning, not a failed start; 0.3.44 took the kasse sites down for two minutes on the rename.

0.3.44 (2026-09-23)

  • The mail stream is named mail (was WORMHOLE); gdo 0.1.2 matches. Delete the old stream on each NATS after upgrading both.

0.3.43 (2026-09-23)

  • Mail is content: mail: { to, subject, body } on a state in aggregates.yaml sends when a record enters that state (to: submitter or one address; {field}, {site}, {chain}, {email} in the templates), and an invite mails its one-time link built in; site.yaml gains mail: { from, reply_to, invite }. Portal renders and publishes on portal.mail.send in the WORMHOLE stream; uhhm/gdo delivers. The lint requires a required email field on every form recording into a bucket that mails the submitter.

0.3.42 (2026-09-23)

  • people: the Kanidm person lookup reads the body, since Kanidm 1.11 answers a missing person with 200 and null rather than 404; before this every invite skipped account creation and failed on the group step

0.3.41 (2026-09-23)

  • publish: the iris release step retries on a fresh clone when its push loses a race against a push to iris main (the v0.3.40 tag never published)
  • iris: check --access prints the access matrix, and every check validates the compiled policy against the schema

0.3.40 (2026-09-23)

  • One access policy, compiled from content into Cedar on every load (src/access.rs): pages, resource reads, desk transitions, self transitions and the automation endpoint all decide through it; decisions carry rule ids, refusals and mutations publish on portal.access.decided, and portal.access.<site>.may answers the same question for other apps over NATS
  • invite: {group} on an alternative onboards a person into Kanidm from a desk (src/people.rs, KANIDM_API_TOKEN), recording the one-time credential link in portal_invites and on portal.people.invited

0.3.39 (2026-09-23)

  • publish: releases the matching uhhm/iris by proxy - re-pins, builds, tests and tags it as this version (needs the IRIS_PUSH_USER variable and IRIS_PUSH_TOKEN secret); iris carries portal’s version number from here on

0.3.38 (2026-09-22)

  • publish: the iris build step gets a writable cargo home; the v0.3.37 tag never published

0.3.37 (2026-09-22)

  • The lint moves to its own repo, uhhm/iris: question_lint becomes iris check, needs_replay becomes iris replay; the tarball ships iris (pinned in publish.yml) instead of question_lint; the needs module and local loaders leave portal

0.3.36 (2026-09-22)

  • Question nav: a followup is offered only once earned - the verified chain answered the alternative leading there, or a link that records nothing leads on from an earned page. Standing on a page no longer unlocks the pages behind its forms; the chain index now records which alternative was answered
  • One bounded HTTP client for every server-side fetch: 20 s budget, size caps per kind of fetch (src/http.rs)
  • Uploads are refused past 20 MB while streaming, and the route body limit that had capped them at axum’s 2 MB default is raised to match
  • tests/jetstream.rs: the store, the event log and the projection tested against a real JetStream (CI starts a throwaway one)
  • content split into gitea, validate, markdown, handlers; every path still content::x
  • This changelog, and the table of what each site runs
  • Business needs as a checkable spec: needs.yaml + question_lint pass
  • needs: success states and a resolved simulation model
  • needs: stages - every stage the business named must be a state
  • needs_replay: make simulated cases real aggregates, report buckets by state
  • needs: also_moved_by - a need carried by more than one group’s desk

0.3.35 (2026-09-12)

  • Item cards: no paragraph margin under the description

0.3.34 (2026-09-11)

  • Question nav: offer followups by reach, not by carrying any chain

0.3.33 (2026-09-02)

  • Head preloads: latin font, wordmark, content-host preconnect

0.3.32 (2026-09-01)

  • Per-site favicon via site.yaml

0.3.31 (2026-09-01)

  • Markdown links allow tel:

0.3.30 (2026-09-01)

  • Single alt-image shows natural height

0.3.29 (2026-09-01)

  • Markdown image layout hints + AVIF assets

0.3.28 (2026-09-01)

  • Hero description renders inline markdown

0.3.27 (2026-09-01)

  • wordmark_invert: explicit, not inferred from .svg

0.3.26 (2026-09-01)

  • Select fields: inline static options

0.3.25 (2026-09-01)

  • Content-shipped stylesheet override (site.yaml stylesheet)

0.3.24 (2026-09-01)

  • Gateway submit-as-link loses the underline

0.3.23 (2026-09-01)

  • Chrome speaks the site’s language: site.yaml lang + i18n table

0.3.22 (2026-09-01)

  • site.yaml wordmark_height; raster logos keep their colors

0.3.21 (2026-09-01)

  • Hero paragraph centers its measure box

0.3.20 (2026-09-01)

  • Sign-in becomes optional: KANIDM_URL unset disables auth cleanly

0.3.19 (2026-09-01)

  • Markdown images in descriptions, gated and framed

0.3.18 (2026-09-01)

  • Item cards render inline markdown descriptions
  • Convergence: ink converges onto the decoded key (WebGL glow)
  • Gesture results overlay the canvas - the page never jumps

0.3.16 (2026-08-30)

  • Announce sweeper: refresh open records from content

0.3.15 (2026-08-30)

  • Hidden fields: carrier value without a label row

0.3.14 (2026-08-30)

  • Feature cards: icon column, one text edge

0.3.13 (2026-08-30)

  • Voice field: nothing leaves the browser until Keep it here

0.3.12 (2026-08-30)

  • Voice preview stays hidden until there is something to play

0.3.11 (2026-08-30)

  • README: gesture and voice fields
  • Validate templated actions against the page pattern

0.3.10 (2026-08-30)

  • type: voice, Requirement.value, playable resource cards

0.3.9 (2026-08-30)

  • Templated actions; places are pickable in the gesture input

0.3.8 (2026-08-30)

  • Descriptions are inline markdown; Feature.link withdrawn

0.3.7 (2026-08-30)

  • Feature.link: a card’s name may point elsewhere

0.3.6 (2026-08-30)

  • Announced pages: event windows, header announcements, summary tasks

0.3.5 (2026-08-30)

  • Gesture widget: stop drawing the decoded ghost path
  • Gesture widget: ghost path back, for v2 keys only

0.3.4 (2026-08-25)

  • Alternative.disabled: announced but not yet takeable

0.3.3 (2026-08-25)

  • Empty list says so before it can parse as zero answers

0.3.2 (2026-08-25)

  • README: hashed pkg assets in the release flow
  • Resource empty text: content-declared, and null counts as empty

0.3.1 (2026-08-25)

  • Content-hashed pkg assets (hash-files) so stale bundles can’t pair with new wasm

0.3.0 (2026-08-25)

  • Hero becomes a content-owned module; site asset proxy; gesture growth fix

0.2.4 (2026-08-25)

  • Gates keep the question nav

0.2.3 (2026-08-25)

  • Attended-bucket lint rule; app-lifetime resources fix first-nav corruption

0.2.2 (2026-08-24)

  • One app-level Title fed by a shared site resource

0.2.1 (2026-08-24)

  • Docs: routing bullet + design doc marked implemented
  • Revalidating cache on app assets; site title survives SPA navigation

0.2.0 (2026-08-24)

  • Initial commit: content-driven onboarding portal
  • Load content from Gitea directly, drop the local clone; add deploy workflow
  • Share sccache between manual dev builds and CI
  • Give CI its own SCCACHE_SERVER_PORT
  • Erase view types at every list/component boundary, not just leaves
  • Redirect cargo-leptos’s tool cache out of act-runner-bare’s StateDirectory
  • Fix Ship release: site-root is project-relative, not CARGO_TARGET_DIR
  • Drop sudo from the deploy workflow - NoNewPrivileges blocks it outright
  • Caddy snippet: use multi-line log block
  • Fix static asset 404s and build-time SITE_NAME
  • Hot-reload content on a NATS trigger instead of requiring a restart
  • Add prosekit rich-text field, Gitea repo embeds, automation KV read endpoint; fix apex/www session-cookie mismatch on /auth/callback
  • Fix prosekit-editor.js: remove bare CSS imports that aborted the whole module
  • Redirect back to the originating page after sign-in, not always /
  • Fix hero-canvas navigation race; style the prosekit editor and add a toolbar
  • yes.js: guard against the canvas not being in the DOM yet
  • Fix get_resource/transition_answer: scope feature lookup to its own alternative
  • Add self-service transitions, authorized by item possession not group membership
  • Event-sourced applicant/subscriber/project aggregates, generalized resources
  • deploy.yml: write GITEA_API_TOKEN for the new GiteaStarred/OrgRepos resource sources
  • Cargo.toml: disambiguate bin-target for cargo-leptos
  • Add manually-triggered backfill workflow, no terminal/sudo needed
  • Trigger redeploy to pick up PORTAL_GITEA_API_TOKEN secret
  • Render generic resource lists as cards, not raw JSON dump
  • Fix recursion-limit build failure in the new item-card renderer
  • Resource-backed multi/single-select requirement
  • Add Organization aggregate (client-as-status), wired but inert
  • Fix favicon (real icon, not an unrelated orange circle) + stack encouragements with the submit button
  • Fix bucket-404 on empty resources, redesign review actions as select-then-confirm, CSS polish
  • Inline encouragements with the submit button, vertically centered
  • Use the real institutional logo for the header wordmark, not plain text
  • Use the actual UHHM-letters wordmark, not the institutional mark
  • Responsive base font size via svmin, not fixed 17px
  • Add plain-unit fallbacks for svmin/svh in case of unsupported engines
  • Size the YES canvas from its container, not window.innerHeight
  • Debounce the YES canvas resize handler and skip no-op resizes
  • Revert canvas sizing to window.innerWidth/innerHeight, gate resize on width change
  • Stop blocking native scroll on the YES canvas’s touch handlers
  • Quicksand: font-display optional, not swap, to stop the text-jump on scroll
  • Use lvh/lvmin, not svh/svmin - svh was the wrong end of the viewport
  • Update style/main.css
  • Update style/main.css
  • YES canvas: size off its own container rect, not window.innerHeight
  • yes.js: freeze .hero-yes’s height via inline style, don’t trust svh alone
  • add responsive container width
  • deploy.yml: build and publish question_lint alongside the app binary
  • Make aggregate state graphs content-driven, not compiled Rust
  • One shared, content-labeled Confirm button per alternative instead of one per row
  • get_resource/get_requirement_options: #[server(default)] on params
  • Add optional Alternative.image and Feature.color/icon for richer layouts
  • Drop the feature list’s left-padding
  • Drop question-report entirely, obfuscate the mailto link instead
  • Transition.from: state graphs deeper than one decision
  • Alternative.images: array of urls, rendered as a Swiper card deck
  • Reseed lost event history from the KV projection on transition
  • Lighten the comment load
  • Validate action targets; format timestamps client-side; clippy cleanup
  • Question nav + gateway alternatives
  • Responsible note under the title; followup pages out of the nav until a chain exists
  • README: what the engine provides
  • Feature icons: masked span painted by currentColor, not
  • Responsible note: one line, same hue and size as the hero description
  • Responsible note as <small>: meta information, dimmer than the description
  • Responsible note back to the footer, above the question nav
  • Bound inputs: a field that loads its value from a sibling-parameterized resource
  • add some more width to our alternatives
  • Accent to a dusty press-cyan, a muted echo of the canvas’s cyan ink
  • Editor type in rem: prosekit’s px typography ignored the responsive root
  • Ban px from stylesheets: every length rem/em off the responsive root
  • No translucent input chrome: solid ink-dim placeholders and embed meta
  • Editor type scales for real: drop prosekit typography.css, fix selectors
  • Stable server fn endpoints: open tabs survive deploys
  • Drop stray local tool temp file, ignore .claude/
  • Noise-driven YES, sticky hero with frosted cards, full light theme
  • increase translucency of laternative cards
  • Showcase jq test tracks Gitea repo shape and website-first links
  • Gesture input type, redoal-relay client, gitea_releases, content-driven branding
  • Echo thumbnails fade in via timeout, not rAF
  • Trigger deploy: redoal OAuth2 credentials now configured
  • Deploy becomes publish: portal ships as a versioned release artifact
  • Semver releases cut with cargo-release; publish on v* tags only
  • Docs: release/rollout flow after the instance-ownership refactor
  • README: frame portal as a generic multi-site question engine
  • Design doc: filesystem routes, sections, dynamic segments
  • Filesystem routes, sections, dynamic segments; instant YES hero

What each site runs

Read from the hosts on 2026-09-28; a rollout is a pin bump in the site’s content repo (PORTAL_RELEASE in its deploy.yml) - on both hosts now, since kasse’s runner deploys its own instances.

SiteHostInstanceVersion
uhhm.noergoapp@uhhm-portalv0.5.2
redoal.comergoapp@redoal-portalv0.5.2
westra.klingenbergbygg.nokasseapp@westra-portalv0.5.2
portal.klingenbergbygg.nokasseapp@klingenberg-portalv0.5.2
vel.klingenbergbygg.nokassetomtervel-portal-1 (podman, tomtervel/infrastructure)v0.6.0, memberships