diff --git a/apps/server/src/review/ReviewService.test.ts b/apps/server/src/review/ReviewService.test.ts index 839eb73b2bb..fc113ba7652 100644 --- a/apps/server/src/review/ReviewService.test.ts +++ b/apps/server/src/review/ReviewService.test.ts @@ -1,19 +1,36 @@ import * as NodeServices from "@effect/platform-node/NodeServices"; import { assert, describe, it } from "@effect/vitest"; +import { type OrchestrationProject, ProjectId } from "@t3tools/contracts"; import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; import * as PlatformError from "effect/PlatformError"; import { ServerConfig } from "../config.ts"; +import * as ProjectionSnapshotQuery from "../orchestration/Services/ProjectionSnapshotQuery.ts"; import * as GitVcsDriver from "../vcs/GitVcsDriver.ts"; import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts"; import * as ReviewService from "./ReviewService.ts"; +function makeProject(workspaceRoot: string): OrchestrationProject { + return { + id: ProjectId.make("project-1"), + title: "Project", + workspaceRoot, + defaultModelSelection: null, + scripts: [], + createdAt: "2026-01-01T00:00:00.000Z", + updatedAt: "2026-01-01T00:00:00.000Z", + deletedAt: null, + }; +} + function makeLayer(input: { readonly workspaceRoot: string; readonly baseDir: string; readonly detectCalls?: Array<{ readonly cwd: string }>; + readonly registeredProjectRoots?: ReadonlyArray; }) { return ReviewService.layer.pipe( Layer.provide( @@ -28,6 +45,16 @@ function makeLayer(input: { }), ), Layer.provide(Layer.mock(GitVcsDriver.GitVcsDriver)({})), + Layer.provide( + Layer.mock(ProjectionSnapshotQuery.ProjectionSnapshotQuery)({ + getActiveProjectByWorkspaceRoot: (workspaceRoot) => + Effect.succeed( + input.registeredProjectRoots?.includes(workspaceRoot) === true + ? Option.some(makeProject(workspaceRoot)) + : Option.none(), + ), + }), + ), Layer.provide(ServerConfig.layerTest(input.workspaceRoot, input.baseDir)), Layer.provideMerge(NodeServices.layer), ); @@ -75,6 +102,34 @@ describe("ReviewService", () => { }).pipe(Effect.provide(NodeServices.layer)), ); + it.effect("allows diff preview cwd matching a registered project workspace root", () => + Effect.gen(function* () { + const fs = yield* FileSystem.FileSystem; + const workspaceRoot = yield* fs.makeTempDirectoryScoped({ prefix: "t3-review-workspace-" }); + const projectRoot = yield* fs.makeTempDirectoryScoped({ prefix: "t3-review-project-" }); + const baseDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-review-base-" }); + const detectCalls: Array<{ readonly cwd: string }> = []; + + const result = yield* Effect.gen(function* () { + const review = yield* ReviewService.ReviewService; + return yield* review.getDiffPreview({ cwd: projectRoot }); + }).pipe( + Effect.provide( + makeLayer({ + workspaceRoot, + baseDir, + detectCalls, + registeredProjectRoots: [projectRoot], + }), + ), + ); + + assert.strictEqual(result.cwd, projectRoot); + assert.deepStrictEqual(result.sources, []); + assert.deepStrictEqual(detectCalls, [{ cwd: projectRoot }]); + }).pipe(Effect.provide(NodeServices.layer)), + ); + it.effect("preserves unexpected path-resolution failures", () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; diff --git a/apps/server/src/review/ReviewService.ts b/apps/server/src/review/ReviewService.ts index db1dc5bc8d2..ee862d0e95d 100644 --- a/apps/server/src/review/ReviewService.ts +++ b/apps/server/src/review/ReviewService.ts @@ -3,6 +3,7 @@ import * as DateTime from "effect/DateTime"; import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; import * as Path from "effect/Path"; import { @@ -14,6 +15,7 @@ import { } from "@t3tools/contracts"; import * as ServerConfig from "../config.ts"; +import * as ProjectionSnapshotQuery from "../orchestration/Services/ProjectionSnapshotQuery.ts"; import * as GitVcsDriver from "../vcs/GitVcsDriver.ts"; import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts"; @@ -30,6 +32,7 @@ export const make = Effect.gen(function* () { const config = yield* ServerConfig.ServerConfig; const fileSystem = yield* FileSystem.FileSystem; const path = yield* Path.Path; + const projectionSnapshotQuery = yield* ProjectionSnapshotQuery.ProjectionSnapshotQuery; const vcsRegistry = yield* VcsDriverRegistry.VcsDriverRegistry; const git = yield* GitVcsDriver.GitVcsDriver; @@ -70,6 +73,25 @@ export const make = Effect.gen(function* () { return; } + const registeredProject = yield* projectionSnapshotQuery + .getActiveProjectByWorkspaceRoot(cwd) + .pipe( + Effect.map(Option.isSome), + Effect.mapError( + (cause) => + new VcsRepositoryDetectionError({ + operation: "ReviewService.assertWorkspaceBoundCwd.resolveRegisteredProject", + cwd, + detail: + "Failed to resolve the registered project while validating the review workspace.", + cause, + }), + ), + ); + if (registeredProject) { + return; + } + return yield* new VcsRepositoryDetectionError({ operation: "ReviewService.getDiffPreview", cwd, diff --git a/apps/web/src/components/DiffPanel.tsx b/apps/web/src/components/DiffPanel.tsx index 5cb2a1cfdea..db1abff996d 100644 --- a/apps/web/src/components/DiffPanel.tsx +++ b/apps/web/src/components/DiffPanel.tsx @@ -322,7 +322,7 @@ export default function DiffPanel({ }, { enabled: isGitRepo && selectedTurn !== undefined }, ); - const primaryBranchDiffPreview = useEnvironmentQuery( + const branchDiffPreview = useEnvironmentQuery( selectedTurnId === null && activeThread && activeCwd ? reviewEnvironment.diffPreview({ environmentId: activeThread.environmentId, @@ -334,26 +334,6 @@ export default function DiffPanel({ }) : null, ); - const shouldRetryBranchDiffAtEnvironmentCwd = - selectedTurnId === null && - primaryBranchDiffPreview.error?.includes("configured workspace root") === true && - serverConfig?.cwd !== undefined && - serverConfig.cwd !== activeCwd; - const fallbackBranchDiffPreview = useEnvironmentQuery( - shouldRetryBranchDiffAtEnvironmentCwd && activeThread && serverConfig - ? reviewEnvironment.diffPreview({ - environmentId: activeThread.environmentId, - input: { - cwd: serverConfig.cwd, - ...(selectedBaseRef ? { baseRef: selectedBaseRef } : {}), - ignoreWhitespace: diffIgnoreWhitespace, - }, - }) - : null, - ); - const branchDiffPreview = shouldRetryBranchDiffAtEnvironmentCwd - ? fallbackBranchDiffPreview - : primaryBranchDiffPreview; const selectedGitSource = branchDiffPreview.data?.sources.find( (source) => source.kind === (selectedGitScope === "unstaged" ? "working-tree" : "branch-range"), );