feat(ui): serve an apple-touch-icon so iOS shows the logo on the home screen (#12917)

iOS ignores the SVG favicon and draws the first letter of the page title
when a page is saved to the home screen. Serve a 180x180 PNG made from
logo.png at /apple-touch-icon.png, link it from the React UI head, and
rewrite the link under a reverse-proxy prefix like the favicon link.

The logo is flattened on white because iOS fills transparency with black.

Assisted-by: Claude Code:claude-sonnet-5-5

Signed-off-by: Mauro Morales <contact@mauromorales.com>
Co-authored-by: Mauro Morales <contact@mauromorales.com>
This commit is contained in:
Mauro AgentandMauro Morales authored and GitHub committed 2026-10-26 09:42:07 +01:00
1 parent 4b4c96852f
commit 53ddea6384
4 files changed
+13

No files matched your search

+11
View File
@@ -389,6 +389,16 @@ func API(application *application.Application) (*echo.Echo, error) {
return c.Blob(http.StatusOK, "image/svg+xml", data)
})
// iOS draws the home-screen icon from an apple-touch-icon PNG and ignores
// the SVG favicon, so it falls back to the first letter of the page title.
e.GET("/apple-touch-icon.png", func(c echo.Context) error {
data, err := embedDirStatic.ReadFile("static/apple-touch-icon.png")
if err != nil {
return c.NoContent(http.StatusNotFound)
}
return c.Blob(http.StatusOK, "image/png", data)
})
// Static files - use fs.Sub to create a filesystem rooted at "static"
staticFS, err := fs.Sub(embedDirStatic, "static")
if err != nil {
@@ -671,6 +681,7 @@ func API(application *application.Application) (*echo.Echo, error) {
html := string(indexHTML)
html = strings.ReplaceAll(html, `="/assets/`, `="`+safePrefix+`assets/`)
html = strings.ReplaceAll(html, `="/favicon.svg"`, `="`+safePrefix+`favicon.svg"`)
html = strings.ReplaceAll(html, `="/apple-touch-icon.png"`, `="`+safePrefix+`apple-touch-icon.png"`)
indexHTML = []byte(html)
}
return c.HTMLBlob(http.StatusOK, indexHTML)
+1
View File
@@ -469,6 +469,7 @@ var _ = Describe("API test", func() {
Expect(string(body)).To(ContainSubstring(`<base href="https://example.org/myprefix/" />`), "body")
Expect(string(body)).ToNot(ContainSubstring(`="/assets/`), "asset URLs must include the prefix")
Expect(string(body)).ToNot(ContainSubstring(`="/favicon.svg"`), "favicon URL must include the prefix")
Expect(string(body)).ToNot(ContainSubstring(`="/apple-touch-icon.png"`), "touch icon URL must include the prefix")
})
// X-Forwarded-Prefix is attacker controllable on misconfigured
+1
View File
@@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>LocalAI</title>
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<script>
// Set the theme before first paint, with the same rule as ThemeContext:
// a stored choice wins, a fresh install opens dark.
Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB