From f9e76a4d245e3a6d024dc6311cead7a83c3e3e30 Mon Sep 17 00:00:00 2001 From: Neville Brem Date: Tue, 31 Mar 2026 20:34:29 +0200 Subject: [PATCH 1/3] fix: add missing static favicon candidates to the resolver --- apps/server/src/project/Layers/ProjectFaviconResolver.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/server/src/project/Layers/ProjectFaviconResolver.ts b/apps/server/src/project/Layers/ProjectFaviconResolver.ts index 3004a7a45cfd..da1250c1f9a9 100644 --- a/apps/server/src/project/Layers/ProjectFaviconResolver.ts +++ b/apps/server/src/project/Layers/ProjectFaviconResolver.ts @@ -27,6 +27,13 @@ const FAVICON_CANDIDATES = [ "assets/icon.png", "assets/logo.svg", "assets/logo.png", + "static/favicon.svg", + "static/favicon.ico", + "static/favicon.png", + "static/icon.svg", + "static/icon.png", + "static/logo.svg", + "static/logo.png", ] as const; // Files that may contain a or icon metadata declaration. From 8c6668db65c05e1742f95e41ced16ae6d227692b Mon Sep 17 00:00:00 2001 From: Neville Brem Date: Tue, 31 Mar 2026 20:52:12 +0200 Subject: [PATCH 2/3] Add 'app.html' to favicon resolver paths --- apps/server/src/project/Layers/ProjectFaviconResolver.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/server/src/project/Layers/ProjectFaviconResolver.ts b/apps/server/src/project/Layers/ProjectFaviconResolver.ts index da1250c1f9a9..a0211d458fea 100644 --- a/apps/server/src/project/Layers/ProjectFaviconResolver.ts +++ b/apps/server/src/project/Layers/ProjectFaviconResolver.ts @@ -45,6 +45,7 @@ const ICON_SOURCE_FILES = [ "app/root.tsx", "src/root.tsx", "src/index.html", + "app.html", ] as const; // Matches tags or object-like icon metadata where rel/href can appear in any order. From f87a54ab74a905e25cfc2347a3879e2f813c1f30 Mon Sep 17 00:00:00 2001 From: Neville Brem Date: Wed, 1 Apr 2026 06:54:28 +0200 Subject: [PATCH 3/3] Add SvelteKit app.html icon resolution support (%sveltekit.assets%) --- .../Layers/ProjectFaviconResolver.test.ts | 18 ++++++++++++++ .../project/Layers/ProjectFaviconResolver.ts | 24 +++++++++++++++---- apps/server/src/projectFaviconRoute.test.ts | 21 ++++++++++++++++ 3 files changed, 59 insertions(+), 4 deletions(-) diff --git a/apps/server/src/project/Layers/ProjectFaviconResolver.test.ts b/apps/server/src/project/Layers/ProjectFaviconResolver.test.ts index 29b30739b9cd..21b93e321a9d 100644 --- a/apps/server/src/project/Layers/ProjectFaviconResolver.test.ts +++ b/apps/server/src/project/Layers/ProjectFaviconResolver.test.ts @@ -60,6 +60,24 @@ it.layer(TestLayer)("ProjectFaviconResolverLive", (it) => { }), ); + it.effect("resolves SvelteKit app icons from src/app.html into static assets", () => + Effect.gen(function* () { + const resolver = yield* ProjectFaviconResolver; + const cwd = yield* makeTempDir; + yield* writeTextFile( + cwd, + "src/app.html", + '', + ); + yield* writeTextFile(cwd, "static/brand/logo.svg", "brand"); + + const resolved = yield* resolver.resolvePath(cwd); + + expect(resolved).not.toBeNull(); + expect(resolved).toContain("static/brand/logo.svg"); + }), + ); + it.effect("returns null when no icon is present", () => Effect.gen(function* () { const resolver = yield* ProjectFaviconResolver; diff --git a/apps/server/src/project/Layers/ProjectFaviconResolver.ts b/apps/server/src/project/Layers/ProjectFaviconResolver.ts index a0211d458fea..299566673775 100644 --- a/apps/server/src/project/Layers/ProjectFaviconResolver.ts +++ b/apps/server/src/project/Layers/ProjectFaviconResolver.ts @@ -45,6 +45,7 @@ const ICON_SOURCE_FILES = [ "app/root.tsx", "src/root.tsx", "src/index.html", + "src/app.html", "app.html", ] as const; @@ -66,9 +67,24 @@ export const makeProjectFaviconResolver = Effect.gen(function* () { const fileSystem = yield* FileSystem.FileSystem; const path = yield* Path.Path; - const resolveIconHref = (projectCwd: string, href: string): string[] => { - const clean = href.replace(/^\//, ""); - return [path.join(projectCwd, "public", clean), path.join(projectCwd, clean)]; + const resolveIconHref = (projectCwd: string, sourcePath: string, href: string): string[] => { + if (/^(?:[a-z]+:)?\/\//i.test(href) || href.startsWith("data:")) { + return []; + } + + const normalizedHref = href.replace(/^%sveltekit\.assets%/, ""); + const clean = normalizedHref.replace(/^\/+/, "").replace(/^\.\//, ""); + const projectCandidates = [ + path.join(projectCwd, "public", clean), + path.join(projectCwd, "static", clean), + path.join(projectCwd, clean), + ]; + + if (href.startsWith("/") || href.startsWith("%sveltekit.assets%")) { + return projectCandidates; + } + + return [path.join(path.dirname(sourcePath), clean), ...projectCandidates]; }; const isPathWithinProject = (projectCwd: string, candidatePath: string): boolean => { @@ -117,7 +133,7 @@ export const makeProjectFaviconResolver = Effect.gen(function* () { if (!href) { continue; } - const existing = yield* findExistingFile(cwd, resolveIconHref(cwd, href)); + const existing = yield* findExistingFile(cwd, resolveIconHref(cwd, sourcePath, href)); if (existing) { return existing; } diff --git a/apps/server/src/projectFaviconRoute.test.ts b/apps/server/src/projectFaviconRoute.test.ts index 99b61aedc923..27d8240449d4 100644 --- a/apps/server/src/projectFaviconRoute.test.ts +++ b/apps/server/src/projectFaviconRoute.test.ts @@ -175,6 +175,27 @@ describe("tryHandleProjectFaviconRequest", () => { }); }); + it("resolves SvelteKit src/app.html icon hrefs from static assets", async () => { + const projectDir = makeTempDir("t3code-favicon-route-sveltekit-static-"); + const iconPath = path.join(projectDir, "static", "brand", "app.svg"); + fs.mkdirSync(path.dirname(iconPath), { recursive: true }); + fs.mkdirSync(path.join(projectDir, "src"), { recursive: true }); + fs.writeFileSync( + path.join(projectDir, "src", "app.html"), + '', + "utf8", + ); + fs.writeFileSync(iconPath, "sveltekit-static", "utf8"); + + await withRouteServer(async (baseUrl) => { + const pathname = `/api/project-favicon?cwd=${encodeURIComponent(projectDir)}`; + const response = await request(baseUrl, pathname); + expect(response.statusCode).toBe(200); + expect(response.contentType).toContain("image/svg+xml"); + expect(response.body).toBe("sveltekit-static"); + }); + }); + it("serves a fallback favicon when no icon exists", async () => { const projectDir = makeTempDir("t3code-favicon-route-fallback-");