Switch auto-generated avatars from dicebear constellation to rings with a walhub-green greyscale-preset background #525

Closed
opened 2026-09-14 17:03:07 +00:00 by crueber · 3 comments
Owner

What's requested

Switch the auto-generated user avatar style from DiceBear constellation to rings, and give the avatar a greyscale-preset-style background rendered in the standard walhub green — the same treatment DiceBear's "indigo duotone" preset applies to its own duotone hue, but in the app's brand color.

Evidence (current behavior)

  • internal/identity/avatar.go:61-67 — constellationStyle parses the styles.Get("constellation") definition once per process.
  • internal/identity/avatar.go:80-94 — GenerateUserAvatarSVG renders with map[string]any{"seed": seed} and default options only. A prior verification noted the constellation style defines no options, so there was no preset/backgroundColor key to set; rings does expose background options (the style definition carries a backgrounds/backgroundColor option group with the standard DiceBear presets, including the greyscale-style flat/duotone backgrounds), which is what makes this treatment possible now.
  • The seed contract (verified email seeds the PRNG; seed must never appear in the output) and the sanitize-first gate (checkAvatarSVG, avatar.go:97-112) must carry over unchanged.

Design notes

  • Walhub green = the Tailwind emerald scale the app already standardizes on (web/src/ui.css: .btn.primary is emerald-600, brand text emerald-700 dark:text-emerald-400, etc. — emerald-600 #059669 is the canonical brand green).
  • "Greyscale-preset-style" means the flat, solid, quietly-toned background DiceBear's greyscale preset produces (a single solid hue behind the rings, no gradients/patterns) — the indigo-duotone analogy is the color choice (a duotone-style figure over a solid brand hue), applied with emerald instead of indigo. The exact emerald step (e.g. 500 vs 600, and whether the dark variant needs a different step) is the implementer's call; light/dark discipline applies to generated bytes too — but note the SVG is generated once server-side, so pick ONE hue that reads on both themes rather than trying to theme-switch the stored SVG (the avatar is served as static bytes per avatar.go's bucket-as-truth model).
  • Style/options plumbing: rename constellationStyle → ringsStyle (or keep a neutral name), switch styles.Get("rings"), and pass the resolved background option through dicebear.NewAvatar alongside seed. Verify against the vendored github.com/dicebear/styles/v10 definition for rings what the option keys actually are (backgroundColor, backgroundType) before wiring — don't guess keys.

Acceptance criteria

  • GenerateUserAvatarSVG renders the DiceBear rings style, not constellation.
  • Generated avatars have a solid walhub-green (emerald) background in the greyscale-preset treatment (flat solid hue behind the rings figure).
  • Seed contract intact: same email → byte-identical SVG; seed string never appears in the output (TestGenerateSeedAbsent stays green).
  • checkAvatarSVG sanitize gate unchanged and still passing (size cap, <svg> prefix, no <script>, seed-leak check).
  • Existing avatar holders keep their current avatars (no retroactive regeneration); new/regenerated avatars use the new style — regeneration via POST /avatar and next-login generation produce the new look.
  • internal/identity/avatar_test.go assertions updated for the new style (any style-name pins, output-shape checks).
  • File-header comment on avatar.go updated (it currently names "constellation" at lines 4 and 70-73).
  • No client/SPA changes needed — the avatar is served as opaque SVG bytes through the existing GET /api/v1/users/{username}/avatar path.
## What's requested Switch the auto-generated user avatar style from DiceBear **constellation** to **rings**, and give the avatar a greyscale-preset-style background rendered in the standard walhub green — the same treatment DiceBear's "indigo duotone" preset applies to its own duotone hue, but in the app's brand color. ## Evidence (current behavior) - `internal/identity/avatar.go:61-67` — `constellationStyle` parses the `styles.Get("constellation")` definition once per process. - `internal/identity/avatar.go:80-94` — `GenerateUserAvatarSVG` renders with `map[string]any{"seed": seed}` and default options only. A prior verification noted the constellation style defines no options, so there was no preset/backgroundColor key to set; **rings** does expose background options (the style definition carries a `backgrounds`/`backgroundColor` option group with the standard DiceBear presets, including the greyscale-style flat/duotone backgrounds), which is what makes this treatment possible now. - The seed contract (verified email seeds the PRNG; seed must never appear in the output) and the sanitize-first gate (`checkAvatarSVG`, avatar.go:97-112) must carry over unchanged. ## Design notes - Walhub green = the Tailwind emerald scale the app already standardizes on (`web/src/ui.css`: `.btn.primary` is `emerald-600`, brand text `emerald-700 dark:text-emerald-400`, etc. — emerald-600 `#059669` is the canonical brand green). - "Greyscale-preset-style" means the flat, solid, quietly-toned background DiceBear's greyscale preset produces (a single solid hue behind the rings, no gradients/patterns) — the indigo-duotone analogy is the color choice (a duotone-style figure over a solid brand hue), applied with emerald instead of indigo. The exact emerald step (e.g. 500 vs 600, and whether the dark variant needs a different step) is the implementer's call; light/dark discipline applies to generated bytes too — but note the SVG is generated once server-side, so pick ONE hue that reads on both themes rather than trying to theme-switch the stored SVG (the avatar is served as static bytes per avatar.go's bucket-as-truth model). - Style/options plumbing: rename `constellationStyle` → `ringsStyle` (or keep a neutral name), switch `styles.Get("rings")`, and pass the resolved background option through `dicebear.NewAvatar` alongside `seed`. Verify against the vendored `github.com/dicebear/styles/v10` definition for `rings` what the option keys actually are (`backgroundColor`, `backgroundType`) before wiring — don't guess keys. ## Acceptance criteria - [ ] `GenerateUserAvatarSVG` renders the DiceBear `rings` style, not `constellation`. - [ ] Generated avatars have a solid walhub-green (emerald) background in the greyscale-preset treatment (flat solid hue behind the rings figure). - [ ] Seed contract intact: same email → byte-identical SVG; seed string never appears in the output (`TestGenerateSeedAbsent` stays green). - [ ] `checkAvatarSVG` sanitize gate unchanged and still passing (size cap, `<svg>` prefix, no `<script>`, seed-leak check). - [ ] Existing avatar holders keep their current avatars (no retroactive regeneration); new/regenerated avatars use the new style — regeneration via POST /avatar and next-login generation produce the new look. - [ ] `internal/identity/avatar_test.go` assertions updated for the new style (any style-name pins, output-shape checks). - [ ] File-header comment on avatar.go updated (it currently names "constellation" at lines 4 and 70-73). - [ ] No client/SPA changes needed — the avatar is served as opaque SVG bytes through the existing `GET /api/v1/users/{username}/avatar` path.
crueber added this to the v1 milestone 2026-09-14 17:03:15 +00:00
Author
Owner

Fixed by #536 (#536): rings style + solid emerald-600 background, seed/sanitize contracts intact.

Fixed by #536 (https://git.packden.us/crueber/walhub/pulls/536): rings style + solid emerald-600 background, seed/sanitize contracts intact.
Author
Owner

Review of PR #536 (fix/issue-525, commit 50e11a0) — verified in scratch worktree /tmp/pr536 (removed afterward). No browser (per instructions; SVG eyeballed as XML).

(1) Rings wired, keys verified — PASS. avatar.go:73 ringsStyle uses styles.Get("rings"); avatar.go:102-105 passes backgroundColor: ["059669"] as core-library option alongside seed. Confirmed against vendored sources: rings.json (styles/v10 v10.6.0) defines no per-style options; dicebear-go v10 (v10.7.0) has zero backgroundType references (v5-era name, correctly not used); background is resolved from the backgroundColor core option (internal/render/renderer.go:37,186; internal/style/options_descriptor.go:67). Functional proof: rendered SVG carries dc:titleRings</dc:title>.
(2) Solid emerald background — PASS. Rendered eyeball SVG (seed eyeball@example.com, len 3030): present; fills are exactly {#059669, #dea552, none, white}; no linearGradient/radialGradient/<pattern; no <svg x="', 'a<b@example.com'); pre-existing TestGenerateSeedAbsent untouched and green; eyeball render contains no seed bytes.
(4) Sanitize gate — PASS, unchanged. Diff contains zero lines touching checkAvatarSVG (avatar.go:117-132 identical to main); size cap, <svg prefix, <script, seed-leak checks all still enforced and exercised by the new test (checkAvatarSVG called per row).
(5) Existing holders untouched — PASS. EnsureAvatarAsync (avatar.go:308-350) unchanged: early return when profile has AvatarContentType or AvatarDisabled, pre- and post-render; no migration/backfill code in diff. New look flows via POST regen and next-login generation (both call GenerateUserAvatarSVG).
(6) No new deps — PASS. go.mod/go.sum diff vs main is empty; changed files are only the 3 identity files + 2 docs. Same dicebear modules under the existing #376 law-1 exception.
(7) Quality gates — PASS. internal/identity: go test -race green; coverage 95.6% statements (≥95 gate); gofmt clean; go vet clean; go build ./... clean. Docs updated in same change (01_identity_permissions.md decision block + 06_server_http.md mention, law 12); file-header comment updated; no web/ changes (correct — opaque SVG bytes); no constellation pins left in code/tests (only historical 'constellation before' notes in docs).

No fixes needed; nothing pushed. MERGE RECOMMENDATION: ready to merge.

Review of PR #536 (fix/issue-525, commit 50e11a0) — verified in scratch worktree /tmp/pr536 (removed afterward). No browser (per instructions; SVG eyeballed as XML). (1) Rings wired, keys verified — PASS. avatar.go:73 ringsStyle uses styles.Get("rings"); avatar.go:102-105 passes backgroundColor: ["059669"] as core-library option alongside seed. Confirmed against vendored sources: rings.json (styles/v10 v10.6.0) defines no per-style options; dicebear-go v10 (v10.7.0) has zero backgroundType references (v5-era name, correctly not used); background is resolved from the backgroundColor core option (internal/render/renderer.go:37,186; internal/style/options_descriptor.go:67). Functional proof: rendered SVG carries <dc:title>Rings</dc:title>. (2) Solid emerald background — PASS. Rendered eyeball SVG (seed eyeball@example.com, len 3030): <rect width="100" height="100" fill="#059669"/> present; fills are exactly {#059669, #dea552, none, white}; no linearGradient/radialGradient/<pattern; no <script>. Hue emerald-600 documented in const comment (avatar.go:60-72) and docs; single-hue-for-both-themes rationale recorded (stored static bytes can't theme-switch). (3) Seed contract — PASS. Same-email determinism + seed-absent checks in new table test over 4 seeds incl. hostile ('></svg><script>alert(1)</script><svg x="', 'a<b@example.com'); pre-existing TestGenerateSeedAbsent untouched and green; eyeball render contains no seed bytes. (4) Sanitize gate — PASS, unchanged. Diff contains zero lines touching checkAvatarSVG (avatar.go:117-132 identical to main); size cap, <svg prefix, <script, seed-leak checks all still enforced and exercised by the new test (checkAvatarSVG called per row). (5) Existing holders untouched — PASS. EnsureAvatarAsync (avatar.go:308-350) unchanged: early return when profile has AvatarContentType or AvatarDisabled, pre- and post-render; no migration/backfill code in diff. New look flows via POST regen and next-login generation (both call GenerateUserAvatarSVG). (6) No new deps — PASS. go.mod/go.sum diff vs main is empty; changed files are only the 3 identity files + 2 docs. Same dicebear modules under the existing #376 law-1 exception. (7) Quality gates — PASS. internal/identity: go test -race green; coverage 95.6% statements (≥95 gate); gofmt clean; go vet clean; go build ./... clean. Docs updated in same change (01_identity_permissions.md decision block + 06_server_http.md mention, law 12); file-header comment updated; no web/ changes (correct — opaque SVG bytes); no constellation pins left in code/tests (only historical 'constellation before' notes in docs). No fixes needed; nothing pushed. MERGE RECOMMENDATION: ready to merge.
Author
Owner

Fixed by PR #536 (review clean — all 7 checks pass, keys verified against vendored sources, seed/sanitize intact), merged. Closing.

Fixed by PR #536 (review clean — all 7 checks pass, keys verified against vendored sources, seed/sanitize intact), merged. Closing.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
crueber/walhub#525
No description provided.