favicon(homepage): move SVG to frontend build (dist/favicon.svg) and serve via scheme; remove hardcoded SVG from main.go; map favicon.ico -> favicon.svg
- commit
- 76aa55bff5b4f3639390a6c1d3a7ae74643b2963
- author
- bamen · · 2025-09-09 20:21 UTC
- committer
- bamen · · 2025-09-09 18:37 UTC
- parent
- e337302
- signature
- unsigned
3 files · +19 -3
M frontend/src/build-html.ts +4 -1
| @@ -8,6 +8,9 @@ function buildHTML() { | |||
| 8 | 8 | const html = HTMLBuilder.createBaseHTML(); | |
| 9 | 9 | const outputPath = join(process.cwd(), 'dist', 'index.html'); | |
| 10 | 10 | writeFileSync(outputPath, html); | |
| 11 | + | // Also emit favicon.svg as a file for dumb:// scheme lookups | |
| 12 | + | const favPath = join(process.cwd(), 'dist', 'favicon.svg'); | |
| 13 | + | writeFileSync(favPath, HTMLBuilder.getFaviconSVG()); | |
| 11 | 14 | console.log('✓ Generated index.html'); | |
| 12 | 15 | } catch (error) { | |
| 13 | 16 | console.error('✗ Failed to generate HTML:', error); | |
| @@ -18,4 +21,4 @@ function buildHTML() { | |||
| 18 | 21 | // Only run when called directly (not imported) | |
| 19 | 22 | if (import.meta.url === `file://${process.argv[1]}`) { | |
| 20 | 23 | buildHTML(); | |
| 21 | - | }⏎̸ | |
| 24 | + | } | |
| @@ -8,6 +8,9 @@ function buildHTML() { | |||||
| 8 | const html = HTMLBuilder.createBaseHTML(); | 8 | const html = HTMLBuilder.createBaseHTML(); | ||
| 9 | const outputPath = join(process.cwd(), 'dist', 'index.html'); | 9 | const outputPath = join(process.cwd(), 'dist', 'index.html'); | ||
| 10 | writeFileSync(outputPath, html); | 10 | writeFileSync(outputPath, html); | ||
| 11 | + | // Also emit favicon.svg as a file for dumb:// scheme lookups | |||
| 12 | + | const favPath = join(process.cwd(), 'dist', 'favicon.svg'); | |||
| 13 | + | writeFileSync(favPath, HTMLBuilder.getFaviconSVG()); | |||
| 11 | console.log('✓ Generated index.html'); | 14 | console.log('✓ Generated index.html'); | ||
| 12 | } catch (error) { | 15 | } catch (error) { | ||
| 13 | console.error('✗ Failed to generate HTML:', error); | 16 | console.error('✗ Failed to generate HTML:', error); | ||
| @@ -18,4 +21,4 @@ function buildHTML() { | |||||
| 18 | // Only run when called directly (not imported) | 21 | // Only run when called directly (not imported) | ||
| 19 | if (import.meta.url === `file://${process.argv[1]}`) { | 22 | if (import.meta.url === `file://${process.argv[1]}`) { | ||
| 20 | buildHTML(); | 23 | buildHTML(); | ||
| 21 | - | }⏎̸ | 24 | + | } |
M frontend/src/utils/html-builder.ts +2 -2
| @@ -35,7 +35,7 @@ export class HTMLBuilder { | |||
| 35 | 35 | } | |
| 36 | 36 | ||
| 37 | 37 | private static getFaviconDataURL(): string { | |
| 38 | - | const svg = this.getFaviconSVG(); | |
| 38 | + | const svg = HTMLBuilder.getFaviconSVG(); | |
| 39 | 39 | // Minimal encoding for safe inline usage | |
| 40 | 40 | const encoded = encodeURIComponent(svg) | |
| 41 | 41 | .replace(/'/g, '%27') | |
| @@ -44,7 +44,7 @@ export class HTMLBuilder { | |||
| 44 | 44 | return `data:image/svg+xml;utf8,${encoded}`; | |
| 45 | 45 | } | |
| 46 | 46 | ||
| 47 | - | private static getFaviconSVG(): string { | |
| 47 | + | static getFaviconSVG(): string { | |
| 48 | 48 | // Stylized D with speed bolt on a subtle dark rounded card | |
| 49 | 49 | return `<?xml version="1.0" encoding="UTF-8"?> | |
| 50 | 50 | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"> | |
| @@ -35,7 +35,7 @@ export class HTMLBuilder { | |||||
| 35 | } | 35 | } | ||
| 36 | 36 | ||||
| 37 | private static getFaviconDataURL(): string { | 37 | private static getFaviconDataURL(): string { | ||
| 38 | - | const svg = this.getFaviconSVG(); | 38 | + | const svg = HTMLBuilder.getFaviconSVG(); |
| 39 | // Minimal encoding for safe inline usage | 39 | // Minimal encoding for safe inline usage | ||
| 40 | const encoded = encodeURIComponent(svg) | 40 | const encoded = encodeURIComponent(svg) | ||
| 41 | .replace(/'/g, '%27') | 41 | .replace(/'/g, '%27') | ||
| @@ -44,7 +44,7 @@ export class HTMLBuilder { | |||||
| 44 | return `data:image/svg+xml;utf8,${encoded}`; | 44 | return `data:image/svg+xml;utf8,${encoded}`; | ||
| 45 | } | 45 | } | ||
| 46 | 46 | ||||
| 47 | - | private static getFaviconSVG(): string { | 47 | + | static getFaviconSVG(): string { |
| 48 | // Stylized D with speed bolt on a subtle dark rounded card | 48 | // Stylized D with speed bolt on a subtle dark rounded card | ||
| 49 | return `<?xml version="1.0" encoding="UTF-8"?> | 49 | return `<?xml version="1.0" encoding="UTF-8"?> | ||
| 50 | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"> | 50 | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"> | ||
M main.go +13 -0
| @@ -287,6 +287,15 @@ func runBrowser() { | |||
| 287 | 287 | rel = "index.html" | |
| 288 | 288 | } | |
| 289 | 289 | } | |
| 290 | + | // Special-case homepage favicon: map .ico request to embedded SVG file | |
| 291 | + | if (u.Host == "homepage" || u.Opaque == "homepage") && strings.EqualFold(rel, "favicon.ico") { | |
| 292 | + | log.Printf("[scheme] asset: rel=%s (host=%s path=%s) → mapping to favicon.svg", rel, u.Host, u.Path) | |
| 293 | + | data, rerr := assets.ReadFile(filepath.ToSlash(filepath.Join("frontend", "dist", "favicon.svg"))) | |
| 294 | + | if rerr == nil { | |
| 295 | + | return "image/svg+xml", data, true | |
| 296 | + | } | |
| 297 | + | } | |
| 298 | + | ||
| 290 | 299 | log.Printf("[scheme] asset: rel=%s (host=%s path=%s)", rel, u.Host, u.Path) | |
| 291 | 300 | data, rerr := assets.ReadFile(filepath.ToSlash(filepath.Join("frontend", "dist", rel))) | |
| 292 | 301 | if rerr != nil { | |
| @@ -720,3 +729,7 @@ func detectAndSetKeyboardLocale() { | |||
| 720 | 729 | // No layout-specific remaps; log for diagnostics only | |
| 721 | 730 | log.Printf("[locale] keyboard locale detected: %s", locale) | |
| 722 | 731 | } | |
| 732 | + | ||
| 733 | + | // getHomepageFaviconSVG returns the SVG used as a favicon for the homepage | |
| 734 | + | // when WebKit requests dumb://homepage/favicon.ico. | |
| 735 | + | // getHomepageFaviconSVG was inlined previously; now favicon.svg is emitted at build time. | |
| @@ -287,6 +287,15 @@ func runBrowser() { | |||||
| 287 | rel = "index.html" | 287 | rel = "index.html" | ||
| 288 | } | 288 | } | ||
| 289 | } | 289 | } | ||
| 290 | + | // Special-case homepage favicon: map .ico request to embedded SVG file | |||
| 291 | + | if (u.Host == "homepage" || u.Opaque == "homepage") && strings.EqualFold(rel, "favicon.ico") { | |||
| 292 | + | log.Printf("[scheme] asset: rel=%s (host=%s path=%s) → mapping to favicon.svg", rel, u.Host, u.Path) | |||
| 293 | + | data, rerr := assets.ReadFile(filepath.ToSlash(filepath.Join("frontend", "dist", "favicon.svg"))) | |||
| 294 | + | if rerr == nil { | |||
| 295 | + | return "image/svg+xml", data, true | |||
| 296 | + | } | |||
| 297 | + | } | |||
| 298 | + | ||||
| 290 | log.Printf("[scheme] asset: rel=%s (host=%s path=%s)", rel, u.Host, u.Path) | 299 | log.Printf("[scheme] asset: rel=%s (host=%s path=%s)", rel, u.Host, u.Path) | ||
| 291 | data, rerr := assets.ReadFile(filepath.ToSlash(filepath.Join("frontend", "dist", rel))) | 300 | data, rerr := assets.ReadFile(filepath.ToSlash(filepath.Join("frontend", "dist", rel))) | ||
| 292 | if rerr != nil { | 301 | if rerr != nil { | ||
| @@ -720,3 +729,7 @@ func detectAndSetKeyboardLocale() { | |||||
| 720 | // No layout-specific remaps; log for diagnostics only | 729 | // No layout-specific remaps; log for diagnostics only | ||
| 721 | log.Printf("[locale] keyboard locale detected: %s", locale) | 730 | log.Printf("[locale] keyboard locale detected: %s", locale) | ||
| 722 | } | 731 | } | ||
| 732 | + | ||||
| 733 | + | // getHomepageFaviconSVG returns the SVG used as a favicon for the homepage | |||
| 734 | + | // when WebKit requests dumb://homepage/favicon.ico. | |||
| 735 | + | // getHomepageFaviconSVG was inlined previously; now favicon.svg is emitted at build time. | |||