feat(kanidm): name the dashboard tiles for users, brand mark on the main tile #284

Sammanfogat
supernaut sammanfogade 1 incheckning från feat/journey-kanidm-tiles in i main 2026-07-31 18:33:36 +00:00
Ägare

Part of the sign-up journey epic. Design and plan are in bitborg-internal (plans/2026-07-31-signup-journey-{design,plan}.md).

⚠️ MERGE AND APPLY THIS BEFORE the bitborg-web PR deploys. The new set-up email tells users to "choose Bitborg" — a promise kept by a display name in this repository. Web-first would send every email in the gap pointing at a tile that still reads "bitborg Forgejo".

Why

After setting a passkey, a new user lands on Kanidm's application dashboard. That tile list is the way onward to Bitborg, but nothing marks it as such — which is why the journey felt like five steps rather than four. We cannot change Kanidm's prose, but the tiles are ours.

Their only signpost today:

displayname: bitborg Forgejo     ← the tile they must click
displayname: bitborg portal

Lowercase brand against the style guide, and "Forgejo"/"portal" are internal names a new user has never heard — on the exact screen where they are working out what to click. Worse, all tiles currently share one icon, because icon-oauth2.svg is bind-mounted over Kanidm's default glyph, so there is no non-textual signal either.

Changes

Client Display name Icon
forgejo bitborg Forgejo → Bitborg logo-square.svg via image_file
bitborg-web bitborg portal → Bitborg Account Dashboard default (generic)
grafana bitborg Grafana → Gitborg Grafana default (generic)

Only the main tile gets the brand mark. The asymmetry is the signal — the thing you came for wears the logo, the others are visibly utilities. Difference in kind reads faster than difference in drawing, and it needs no new assets.

image_file is a path inside the provisioning container, which previously mounted only /state.json, so the icon needed a second -v. The file is already staged by the existing branding task.

Bitborg Account Dashboard is Title Case by decision (a product-name label), against the guide's sentence-case rule.

Safety — verified, not assumed

The forgejo client carries seven claim maps, one of which (forgejo_role → forgejo_admins → "admin") grants Forgejo site-admin. Dropping it would silently demote every admin at their next login.

The final reviewer rendered the Jinja template offline against pre- and post-diff variables and diffed the parsed JSON, which is stronger evidence than a --check diff. The complete set of rendered differences is:

  • forgejo.displayName, bitborg-web.displayName, grafana.displayName
  • forgejo.imageFile (absent → /icons/logo-square.svg)

originUrl, originLanding, preferShortUsername, scopeMaps, claimMaps, and the entire groups and persons blocks are byte-identical. That matters especially for scope maps, which this provisioning tool applies additively and cannot remove.

Also confirmed: image_file genuinely exists in kanidm-provision v1.3.0's Oauth2System (camelCase, no deny_unknown_fields to trip on), and our 2,971-byte SVG passes Kanidm 1.10.4's validation (svg::read() plus a 256 KB cap). update_oauth2_image runs last in sync_oauth2s(), so even a failed upload could not leave claim maps half-written.

Verification

ansible-playbook site.yml --syntax-check clean; ansible-lint roles/kanidm/ passes on the production profile. Dry-run showed exactly one changed task (the state render), with the account-policy gate still reporting mfa and the scope-map drift gate quiet on all four clients.

The provisioning run itself cannot be dry-run — it is a command, skipped under --check. The rendered state is verified; the mutation is not.

Post-apply checks

  1. kanidm system oauth2 get forgejo — confirm origins unchanged and all seven claim maps present, including forgejo_role.
  2. Log in to Forgejo as an admin — the one failure that would be loud.
  3. Confirm the renamed tiles appear for a non-admin member, and whether imageFile actually overrode the bind-mounted default glyph. That last one is a known unverified assumption; if it did not take effect, the naming was already doing most of the work — do not start patching Kanidm.

Known follow-up

The icon mount is unconditional, but its source file is staged only when: kanidm_custom_branding. With branding disabled — plausible while debugging a Kanidm upgrade — podman would create the missing path as a directory and the image read would abort the whole kanidm converge. Default is true and nothing overrides it, so production is unaffected. Worth gating image_file on the same flag.

Part of the sign-up journey epic. Design and plan are in bitborg-internal (`plans/2026-07-31-signup-journey-{design,plan}.md`). **⚠️ MERGE AND APPLY THIS BEFORE the bitborg-web PR deploys.** The new set-up email tells users to "choose **Bitborg**" — a promise kept by a display name in this repository. Web-first would send every email in the gap pointing at a tile that still reads "bitborg Forgejo". ## Why After setting a passkey, a new user lands on Kanidm's application dashboard. That tile list **is** the way onward to Bitborg, but nothing marks it as such — which is why the journey felt like five steps rather than four. We cannot change Kanidm's prose, but the tiles are ours. Their only signpost today: ``` displayname: bitborg Forgejo ← the tile they must click displayname: bitborg portal ``` Lowercase brand against the style guide, and "Forgejo"/"portal" are internal names a new user has never heard — on the exact screen where they are working out what to click. Worse, **all tiles currently share one icon**, because `icon-oauth2.svg` is bind-mounted over Kanidm's default glyph, so there is no non-textual signal either. ## Changes | Client | Display name | Icon | | --- | --- | --- | | `forgejo` | `bitborg Forgejo` → **`Bitborg`** | `logo-square.svg` via `image_file` | | `bitborg-web` | `bitborg portal` → **`Bitborg Account Dashboard`** | default (generic) | | `grafana` | `bitborg Grafana` → **`Gitborg Grafana`** | default (generic) | **Only the main tile gets the brand mark.** The asymmetry is the signal — the thing you came for wears the logo, the others are visibly utilities. Difference in *kind* reads faster than difference in *drawing*, and it needs no new assets. `image_file` is a path *inside* the provisioning container, which previously mounted only `/state.json`, so the icon needed a second `-v`. The file is already staged by the existing branding task. `Bitborg Account Dashboard` is Title Case by decision (a product-name label), against the guide's sentence-case rule. ## Safety — verified, not assumed The `forgejo` client carries **seven claim maps**, one of which (`forgejo_role` → `forgejo_admins` → "admin") grants Forgejo **site-admin**. Dropping it would silently demote every admin at their next login. The final reviewer rendered the Jinja template offline against pre- and post-diff variables and diffed the **parsed JSON**, which is stronger evidence than a `--check` diff. The complete set of rendered differences is: - `forgejo.displayName`, `bitborg-web.displayName`, `grafana.displayName` - `forgejo.imageFile` (absent → `/icons/logo-square.svg`) `originUrl`, `originLanding`, `preferShortUsername`, `scopeMaps`, `claimMaps`, and the entire `groups` and `persons` blocks are **byte-identical**. That matters especially for scope maps, which this provisioning tool applies additively and cannot remove. Also confirmed: `image_file` genuinely exists in kanidm-provision v1.3.0's `Oauth2System` (camelCase, no `deny_unknown_fields` to trip on), and our 2,971-byte SVG passes Kanidm 1.10.4's validation (`svg::read()` plus a 256 KB cap). `update_oauth2_image` runs last in `sync_oauth2s()`, so even a failed upload could not leave claim maps half-written. ## Verification `ansible-playbook site.yml --syntax-check` clean; `ansible-lint roles/kanidm/` passes on the production profile. Dry-run showed exactly one changed task (the state render), with the account-policy gate still reporting `mfa` and the scope-map drift gate quiet on all four clients. **The provisioning run itself cannot be dry-run** — it is a `command`, skipped under `--check`. The rendered state is verified; the mutation is not. ## Post-apply checks 1. `kanidm system oauth2 get forgejo` — confirm origins unchanged and **all seven claim maps present**, including `forgejo_role`. 2. **Log in to Forgejo as an admin** — the one failure that would be loud. 3. Confirm the renamed tiles appear for a non-admin member, and whether `imageFile` actually overrode the bind-mounted default glyph. That last one is a known unverified assumption; if it did not take effect, the naming was already doing most of the work — do not start patching Kanidm. ## Known follow-up The icon mount is unconditional, but its source file is staged only `when: kanidm_custom_branding`. With branding disabled — plausible while debugging a Kanidm upgrade — podman would create the missing path as a directory and the image read would abort the whole kanidm converge. Default is `true` and nothing overrides it, so production is unaffected. Worth gating `image_file` on the same flag.
supernaut lade till 1 incheckning 2026-07-31 18:21:16 +00:00
feat(kanidm): name the dashboard tiles for users, brand mark on the main tile
Alla kontroller lyckades
ci / ci (pull_request) Successful in 1m26s
5ff20ad31f
supernaut sammanfogade incheckning a3c760c180 till main 2026-07-31 18:33:36 +00:00
supernaut tog bort grenen feat/journey-kanidm-tiles 2026-07-31 18:33:36 +00:00
Logga in för att delta i denna konversation.
Inga granskare
Ingen milstolpe
Inget projekt
Inga tilldelade
1 deltagare
Notiser
Förfallodatum
Förfallodatumet är ogiltigt eller utanför gränserna. Använd formatet "åååå-mm-dd".

Inget förfallodatum satt.

Beroenden

Inga beroenden satta

Referens
bitborg/bitborg-infra!284
Ingen beskrivning angiven.