Generated monogram fallback + optional dark-mode icon per application
Some checks failed
CI / scan_ruby (push) Has been cancelled
CI / scan_js (push) Has been cancelled
CI / scan_container (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / test (push) Has been cancelled
CI / system-test (push) Has been cancelled

When an application has no icon attached, render a deterministic
monogram SVG instead of the generic picture-frame placeholder. Initials
are picked from capital letters in the name (ShelfLife -> SL); fall
back to the first two letters when fewer than two capitals exist
(Audiobookshelf -> AU). Background colour is hashed from the name for
stable per-app identity across visits.

Adds an optional second icon attachment, icon_dark, alongside the main
icon. When present, render a <picture> with a prefers-color-scheme:
dark source so the browser swaps automatically; when absent, the main
icon is used in both modes. The SVG sanitization, content-type fix,
and size/format validation now run over both attachments uniformly.

Bumps to 0.14.0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dan Milne
2026-06-07 17:02:53 +10:00
parent 5b41db2c6a
commit bfad9c4e9d
12 changed files with 201 additions and 65 deletions

View File

@@ -29,4 +29,31 @@ class ApplicationTest < ActiveSupport::TestCase
tempfile&.close
tempfile&.unlink
end
test "icon_dark is independently attachable and SVG-sanitized" do
app = applications(:kavita_app)
svg = %(<svg xmlns="http://www.w3.org/2000/svg"><script>boom()</script><circle cx="5" cy="5" r="3"/></svg>)
tempfile = Tempfile.new(["dark", ".svg"]).tap do |t|
t.write(svg)
t.rewind
end
uploaded = ActionDispatch::Http::UploadedFile.new(
tempfile: tempfile,
filename: "dark.svg",
type: "image/svg+xml"
)
assert_nothing_raised do
app.update!(icon_dark: uploaded)
end
assert app.icon_dark.attached?
cleaned = app.icon_dark.download
refute_match(/<script/i, cleaned)
assert_match(/<circle/, cleaned)
ensure
tempfile&.close
tempfile&.unlink
end
end