From 1c45b0d78855c2abd1976ea79ee6b08b1ede01d2 Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Tue, 6 Oct 2026 14:11:02 -0400 Subject: [PATCH 1/4] fix(server): missing project folders no longer log favicon warnings Signed-off-by: Yordis Prieto --- .../project/ProjectFaviconResolver.test.ts | 20 +++++++++++++++---- .../src/project/ProjectFaviconResolver.ts | 11 +++++++++- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/apps/server/src/project/ProjectFaviconResolver.test.ts b/apps/server/src/project/ProjectFaviconResolver.test.ts index f86e8f8aa399..65739624f820 100644 --- a/apps/server/src/project/ProjectFaviconResolver.test.ts +++ b/apps/server/src/project/ProjectFaviconResolver.test.ts @@ -352,20 +352,32 @@ it.layer(TestLayer)("ProjectFaviconResolverLive", (it) => { }), ); + it.effect("returns null when the workspace root no longer exists", () => + Effect.gen(function* () { + const resolver = yield* ProjectFaviconResolver.ProjectFaviconResolver; + const cwd = yield* makeTempDir; + + const resolved = yield* resolver.resolvePath(`${cwd}/missing`); + + expect(resolved).toBeNull(); + }), + ); + it.effect("preserves workspace normalization context", () => Effect.gen(function* () { const resolver = yield* ProjectFaviconResolver.ProjectFaviconResolver; const cwd = yield* makeTempDir; - const missingCwd = `${cwd}/missing`; + yield* writeTextFile(cwd, "not-a-directory", ""); + const fileCwd = `${cwd}/not-a-directory`; - const error = yield* resolver.resolvePath(missingCwd).pipe(Effect.flip); + const error = yield* resolver.resolvePath(fileCwd).pipe(Effect.flip); expect(error).toMatchObject({ _tag: "ProjectFaviconResolutionError", operation: "normalize-workspace", - workspaceRoot: missingCwd, + workspaceRoot: fileCwd, }); - expect(error.cause).toBeInstanceOf(WorkspacePaths.WorkspaceRootNotExistsError); + expect(error.cause).toBeInstanceOf(WorkspacePaths.WorkspaceRootNotDirectoryError); }), ); diff --git a/apps/server/src/project/ProjectFaviconResolver.ts b/apps/server/src/project/ProjectFaviconResolver.ts index 90cbcb574e81..06472112e144 100644 --- a/apps/server/src/project/ProjectFaviconResolver.ts +++ b/apps/server/src/project/ProjectFaviconResolver.ts @@ -209,7 +209,12 @@ export const make = Effect.gen(function* () { cwd: string, faviconPath?: string, ): Effect.fn.Return { - const projectCwd = yield* workspacePaths.normalizeWorkspaceRoot(cwd).pipe( + // A project whose checkout was moved or deleted has no icon to show. + const normalizedCwd = yield* workspacePaths.normalizeWorkspaceRoot(cwd).pipe( + Effect.asSome, + Effect.catchTags({ + WorkspaceRootNotExistsError: () => Effect.succeed(Option.none()), + }), Effect.mapError( (cause) => new ProjectFaviconResolutionError({ @@ -219,6 +224,10 @@ export const make = Effect.gen(function* () { }), ), ); + if (Option.isNone(normalizedCwd)) { + return null; + } + const projectCwd = normalizedCwd.value; // A grouped project's saved path can be absent from one checkout. Use it // where it exists and retain automatic discovery for the other checkouts. if (faviconPath !== undefined) { From 97d11cbea0acc5b831a3891100a4857197a2ded2 Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Tue, 6 Oct 2026 14:43:24 -0400 Subject: [PATCH 2/4] fix(server): keep missing workspace favicon lookups uncached Signed-off-by: Yordis Prieto --- .../project/ProjectEnrichmentService.test.ts | 52 +++++++++++++++++++ .../src/project/ProjectEnrichmentService.ts | 8 +++ .../project/ProjectFaviconResolver.test.ts | 20 ++----- .../src/project/ProjectFaviconResolver.ts | 17 +++--- 4 files changed, 71 insertions(+), 26 deletions(-) diff --git a/apps/server/src/project/ProjectEnrichmentService.test.ts b/apps/server/src/project/ProjectEnrichmentService.test.ts index a5f050fe9d04..fb69669757fd 100644 --- a/apps/server/src/project/ProjectEnrichmentService.test.ts +++ b/apps/server/src/project/ProjectEnrichmentService.test.ts @@ -3,9 +3,12 @@ import type { RepositoryIdentity } from "@t3tools/contracts"; import * as Deferred from "effect/Deferred"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; +import * as Logger from "effect/Logger"; import * as PubSub from "effect/PubSub"; import * as Ref from "effect/Ref"; +import * as WorkspacePaths from "../workspace/WorkspacePaths.ts"; + import * as ProjectEnrichment from "./ProjectEnrichmentService.ts"; import * as ProjectFaviconResolver from "./ProjectFaviconResolver.ts"; import * as RepositoryIdentityResolver from "./RepositoryIdentityResolver.ts"; @@ -94,6 +97,55 @@ it.effect("preserves either enrichment field when the other resolver fails", () }), ); +it.effect("does not warn about favicons for workspace roots that no longer exist", () => + Effect.gen(function* () { + const warnings: Array = []; + const logger = Logger.make(({ message }) => { + warnings.push(message); + }); + const metadataLayer = Layer.merge( + Layer.succeed(RepositoryIdentityResolver.RepositoryIdentityResolver, { + resolve: (workspaceRoot) => Effect.succeed(identity(workspaceRoot)), + }), + Layer.succeed(ProjectFaviconResolver.ProjectFaviconResolver, { + resolvePath: (workspaceRoot) => + Effect.fail( + workspaceRoot === "/missing" + ? new ProjectFaviconResolver.ProjectFaviconResolutionError({ + operation: "normalize-workspace", + workspaceRoot, + cause: new WorkspacePaths.WorkspaceRootNotExistsError({ + workspaceRoot, + normalizedWorkspaceRoot: workspaceRoot, + }), + }) + : new ProjectFaviconResolver.ProjectFaviconResolutionError({ + operation: "stat-candidate", + workspaceRoot, + cause: "favicon resolver failed", + }), + ), + }), + ); + + yield* Effect.gen(function* () { + const service = yield* ProjectEnrichment.ProjectEnrichmentService; + yield* service.request("/missing"); + yield* service.request("/broken"); + + for (let attempt = 0; attempt < 100 && warnings.length === 0; attempt += 1) { + yield* Effect.yieldNow; + } + + assert.lengthOf(warnings, 1); + assert.nestedPropertyVal(warnings[0], "[1].workspaceRoot", "/broken"); + }).pipe( + Effect.provide(makeLayer(metadataLayer, { concurrency: 1 })), + Effect.provide(Logger.layer([logger], { mergeWithExisting: false })), + ); + }), +); + it.effect("publishes repository completion while favicon enrichment is still pending", () => Effect.gen(function* () { const releaseFavicon = yield* Deferred.make(); diff --git a/apps/server/src/project/ProjectEnrichmentService.ts b/apps/server/src/project/ProjectEnrichmentService.ts index 93f6c5c854a1..a9cff599e19d 100644 --- a/apps/server/src/project/ProjectEnrichmentService.ts +++ b/apps/server/src/project/ProjectEnrichmentService.ts @@ -185,6 +185,14 @@ export const make = Effect.fn("ProjectEnrichmentService.make")(function* ( workspaceRoot: string, ) { const faviconPath = yield* Cache.get(faviconCache, workspaceRoot); + if ( + Option.exists( + Exit.findErrorOption(faviconPath), + ProjectFaviconResolver.isMissingWorkspaceRoot, + ) + ) { + return; + } yield* logFailure(workspaceRoot, "faviconPath", faviconPath); }); diff --git a/apps/server/src/project/ProjectFaviconResolver.test.ts b/apps/server/src/project/ProjectFaviconResolver.test.ts index 65739624f820..f86e8f8aa399 100644 --- a/apps/server/src/project/ProjectFaviconResolver.test.ts +++ b/apps/server/src/project/ProjectFaviconResolver.test.ts @@ -352,32 +352,20 @@ it.layer(TestLayer)("ProjectFaviconResolverLive", (it) => { }), ); - it.effect("returns null when the workspace root no longer exists", () => - Effect.gen(function* () { - const resolver = yield* ProjectFaviconResolver.ProjectFaviconResolver; - const cwd = yield* makeTempDir; - - const resolved = yield* resolver.resolvePath(`${cwd}/missing`); - - expect(resolved).toBeNull(); - }), - ); - it.effect("preserves workspace normalization context", () => Effect.gen(function* () { const resolver = yield* ProjectFaviconResolver.ProjectFaviconResolver; const cwd = yield* makeTempDir; - yield* writeTextFile(cwd, "not-a-directory", ""); - const fileCwd = `${cwd}/not-a-directory`; + const missingCwd = `${cwd}/missing`; - const error = yield* resolver.resolvePath(fileCwd).pipe(Effect.flip); + const error = yield* resolver.resolvePath(missingCwd).pipe(Effect.flip); expect(error).toMatchObject({ _tag: "ProjectFaviconResolutionError", operation: "normalize-workspace", - workspaceRoot: fileCwd, + workspaceRoot: missingCwd, }); - expect(error.cause).toBeInstanceOf(WorkspacePaths.WorkspaceRootNotDirectoryError); + expect(error.cause).toBeInstanceOf(WorkspacePaths.WorkspaceRootNotExistsError); }), ); diff --git a/apps/server/src/project/ProjectFaviconResolver.ts b/apps/server/src/project/ProjectFaviconResolver.ts index 06472112e144..ad7200b54d33 100644 --- a/apps/server/src/project/ProjectFaviconResolver.ts +++ b/apps/server/src/project/ProjectFaviconResolver.ts @@ -110,6 +110,12 @@ export class ProjectFaviconResolutionError extends Schema.TaggedError + error.operation === "normalize-workspace" && isWorkspaceRootNotExistsError(error.cause); + /** Service tag for project favicon resolution. */ export class ProjectFaviconResolver extends Context.Service< ProjectFaviconResolver, @@ -209,12 +215,7 @@ export const make = Effect.gen(function* () { cwd: string, faviconPath?: string, ): Effect.fn.Return { - // A project whose checkout was moved or deleted has no icon to show. - const normalizedCwd = yield* workspacePaths.normalizeWorkspaceRoot(cwd).pipe( - Effect.asSome, - Effect.catchTags({ - WorkspaceRootNotExistsError: () => Effect.succeed(Option.none()), - }), + const projectCwd = yield* workspacePaths.normalizeWorkspaceRoot(cwd).pipe( Effect.mapError( (cause) => new ProjectFaviconResolutionError({ @@ -224,10 +225,6 @@ export const make = Effect.gen(function* () { }), ), ); - if (Option.isNone(normalizedCwd)) { - return null; - } - const projectCwd = normalizedCwd.value; // A grouped project's saved path can be absent from one checkout. Use it // where it exists and retain automatic discovery for the other checkouts. if (faviconPath !== undefined) { From cd5f88b4e1f76192c5911e4e9ac9d1b5398de1c9 Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Tue, 6 Oct 2026 14:46:37 -0400 Subject: [PATCH 3/4] refactor(server): name the missing workspace favicon check Signed-off-by: Yordis Prieto --- apps/server/src/project/ProjectEnrichmentService.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/apps/server/src/project/ProjectEnrichmentService.ts b/apps/server/src/project/ProjectEnrichmentService.ts index a9cff599e19d..17bde01d1e9a 100644 --- a/apps/server/src/project/ProjectEnrichmentService.ts +++ b/apps/server/src/project/ProjectEnrichmentService.ts @@ -185,12 +185,11 @@ export const make = Effect.fn("ProjectEnrichmentService.make")(function* ( workspaceRoot: string, ) { const faviconPath = yield* Cache.get(faviconCache, workspaceRoot); - if ( - Option.exists( - Exit.findErrorOption(faviconPath), - ProjectFaviconResolver.isMissingWorkspaceRoot, - ) - ) { + const isMissingWorkspaceRoot = Option.exists( + Exit.findErrorOption(faviconPath), + ProjectFaviconResolver.isMissingWorkspaceRoot, + ); + if (isMissingWorkspaceRoot) { return; } yield* logFailure(workspaceRoot, "faviconPath", faviconPath); From aa6c96ba7cb200833138ac6baee101097af99565 Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Tue, 6 Oct 2026 14:55:51 -0400 Subject: [PATCH 4/4] test(server): provide the capture logger through the enrichment layer Signed-off-by: Yordis Prieto --- apps/server/src/project/ProjectEnrichmentService.test.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/server/src/project/ProjectEnrichmentService.test.ts b/apps/server/src/project/ProjectEnrichmentService.test.ts index fb69669757fd..4fe3dbbfa824 100644 --- a/apps/server/src/project/ProjectEnrichmentService.test.ts +++ b/apps/server/src/project/ProjectEnrichmentService.test.ts @@ -140,8 +140,11 @@ it.effect("does not warn about favicons for workspace roots that no longer exist assert.lengthOf(warnings, 1); assert.nestedPropertyVal(warnings[0], "[1].workspaceRoot", "/broken"); }).pipe( - Effect.provide(makeLayer(metadataLayer, { concurrency: 1 })), - Effect.provide(Logger.layer([logger], { mergeWithExisting: false })), + Effect.provide( + makeLayer(metadataLayer, { concurrency: 1 }).pipe( + Layer.provide(Logger.layer([logger], { mergeWithExisting: false })), + ), + ), ); }), );