import { EnvironmentId, ProjectId, type AgentSessionProjectCandidate } from "@t3tools/contracts";import { describe, expect, it } from "vite-plus/test"; import { groupOnboardingProjects, partitionOnboardingProjects, onboardingProjectKey, resolveOnboardingLandingProject, resolveOnboardingProjectId,} from "./projectImport.logic"; const now = Date.parse("2026-08-22T12:00:00.000Z"); function candidate( path: string, overrides: Partial<AgentSessionProjectCandidate> = {},): AgentSessionProjectCandidate { return { title: path.split("/").at(-1) ?? path, path, sources: ["codex"], threadCount: 3, lastActiveAt: "2026-08-20T12:00:00.000Z", alreadyImported: false, git: { remoteKey: null, repository: null }, ...overrides, };} const github = (repository: string) => ({ remoteKey: `github.com/${repository.toLowerCase()}`, repository,}); describe("partitionOnboardingProjects", () => { it("keeps existing projects available for thread history import", () => { const imported = candidate("/projects/current", { alreadyImported: true }); const available = candidate("/projects/other"); expect(partitionOnboardingProjects([imported, available], now)).toEqual({ available: [imported, available], recent: [imported, available], }); }); it("keeps projects older than 30 days out of the default selection", () => { const recent = candidate("/projects/recent"); const older = candidate("/projects/older", { lastActiveAt: "2026-07-01T12:00:00.000Z", }); expect(partitionOnboardingProjects([recent, older], now)).toEqual({ available: [recent, older], recent: [recent], }); }); it("keeps future activity out of the default selection", () => { const recent = candidate("/projects/recent"); const future = candidate("/projects/future", { lastActiveAt: "2026-08-23T12:00:00.000Z", }); expect(partitionOnboardingProjects([recent, future], now)).toEqual({ available: [recent, future], recent: [recent], }); }); it("keeps non-git folders and thin histories out of the default selection", () => { const repo = candidate("/projects/repo"); const folder = candidate("/projects/folder", { git: null }); const thin = candidate("/projects/thin", { threadCount: 2 }); expect(partitionOnboardingProjects([repo, folder, thin], now).recent).toEqual([repo]); }); it("selects candidates from servers that do not report git identity", () => { const { git: _git, ...legacy } = candidate("/projects/legacy"); expect(partitionOnboardingProjects([legacy], now).recent).toEqual([legacy]); });}); describe("groupOnboardingProjects", () => { it("groups clones by origin, keeps local repos separate, and folds non-git folders away", () => { const main = candidate("/code/t3code", { git: github("pingdotgg/t3code"), threadCount: 79, lastActiveAt: "2026-08-21T12:00:00.000Z", }); const clone = candidate("/code/clones/t3code-2", { git: github("pingdotgg/t3code"), threadCount: 13, lastActiveAt: "2026-08-10T12:00:00.000Z", }); const older = candidate("/code/fleet", { git: github("t3dotgg/fleet"), threadCount: 295, lastActiveAt: "2026-08-22T00:00:00.000Z", }); const local = candidate("/code/scratch-repo", { title: "scratch-repo" }); const folder = candidate("/tmp/notes", { git: null }); const grouped = groupOnboardingProjects([main, clone, older, local, folder]); expect(grouped.other).toEqual([folder]); expect( grouped.repositories.map((group) => ({ label: group.label, paths: group.candidates.map((item) => item.path), threadCount: group.threadCount, lastActiveAt: group.lastActiveAt, })), ).toEqual([ { label: "t3dotgg/fleet", paths: ["/code/fleet"], threadCount: 295, lastActiveAt: "2026-08-22T00:00:00.000Z", }, { label: "pingdotgg/t3code", paths: ["/code/t3code", "/code/clones/t3code-2"], threadCount: 92, lastActiveAt: "2026-08-21T12:00:00.000Z", }, { label: "scratch-repo", paths: ["/code/scratch-repo"], threadCount: 3, lastActiveAt: "2026-08-20T12:00:00.000Z", }, ]); }); it("lists candidates without git identity as standalone repositories", () => { const { git: _git, ...legacy } = candidate("/code/legacy", { title: "legacy" }); const grouped = groupOnboardingProjects([legacy]); expect(grouped.other).toEqual([]); expect(grouped.repositories.map((group) => [group.label, group.repository])).toEqual([ ["legacy", null], ]); });}); describe("resolveOnboardingProjectId", () => { const localEnvironmentId = EnvironmentId.make("local"); const remoteEnvironmentId = EnvironmentId.make("remote"); const localProjectId = ProjectId.make("local-project"); it("uses the scanned project ID before the project reaches the client", () => { expect( resolveOnboardingProjectId( [], localEnvironmentId, candidate("/projects/repo", { projectId: localProjectId }), ), ).toBe(localProjectId); }); it("uses the scanned project ID when the client still has an older project at that root", () => { expect( resolveOnboardingProjectId( [ { id: ProjectId.make("stale-project"), environmentId: localEnvironmentId, workspaceRoot: "/projects/repo", }, ], localEnvironmentId, candidate("/projects/repo", { projectId: localProjectId }), ), ).toBe(localProjectId); }); it("returns null to create a project when neither the scan nor the client has a project ID", () => { expect( resolveOnboardingProjectId([], localEnvironmentId, candidate("/projects/new")), ).toBeNull(); }); it("finds an existing project by normalized root in the target environment", () => { expect( resolveOnboardingProjectId( [ { id: ProjectId.make("remote-project"), environmentId: remoteEnvironmentId, workspaceRoot: "C:\\Work\\Repo", }, { id: localProjectId, environmentId: localEnvironmentId, workspaceRoot: "C:\\Work\\Repo\\", }, ], localEnvironmentId, candidate("c:/work/repo"), ), ).toBe(localProjectId); }); it("does not reuse a project from another environment", () => { expect( resolveOnboardingProjectId( [ { id: ProjectId.make("remote-project"), environmentId: remoteEnvironmentId, workspaceRoot: "/projects/repo", }, ], localEnvironmentId, candidate("/projects/repo"), ), ).toBeNull(); }); it("finds an alias after the scanner returns its persisted project root", () => { expect( resolveOnboardingProjectId( [ { id: localProjectId, environmentId: localEnvironmentId, workspaceRoot: "/real/projects/repo", }, ], localEnvironmentId, candidate("/real/projects/repo"), ), ).toBe(localProjectId); }); it("finds the current root owner when the scan has no project ID", () => { const recreatedProjectId = ProjectId.make("recreated-project"); expect( resolveOnboardingProjectId( [ { id: localProjectId, environmentId: localEnvironmentId, workspaceRoot: "/projects/other", }, { id: recreatedProjectId, environmentId: localEnvironmentId, workspaceRoot: "/projects/repo", }, ], localEnvironmentId, candidate("/projects/repo"), ), ).toBe(recreatedProjectId); }); it("does not reuse a moved project when the scan has no project ID", () => { expect( resolveOnboardingProjectId( [ { id: localProjectId, environmentId: localEnvironmentId, workspaceRoot: "/projects/moved", }, ], localEnvironmentId, candidate("/projects/repo"), ), ).toBeNull(); });}); describe("resolveOnboardingLandingProject", () => { it("skips a failed first project for a later project with imported history", () => { expect( resolveOnboardingLandingProject( ["/projects/failed", "/projects/imported"], new Map([["/projects/imported", "imported"]]), new Map([["/projects/imported", "imported"]]), ), ).toBe("imported"); }); it("prefers a partial first import that added history", () => { expect( resolveOnboardingLandingProject( ["/projects/partial", "/projects/complete"], new Map([["/projects/partial", "partial"]]), new Map([["/projects/complete", "complete"]]), ), ).toBe("partial"); }); it("uses a completed zero-history project when no import added history", () => { expect( resolveOnboardingLandingProject( ["/projects/empty", "/projects/failed"], new Map(), new Map([["/projects/empty", "empty"]]), ), ).toBe("empty"); }); it("keeps an earlier successful import available on retry", () => { expect( resolveOnboardingLandingProject( ["/projects/imported", "/projects/retry"], new Map([["/projects/imported", "imported"]]), new Map([["/projects/imported", "imported"]]), ), ).toBe("imported"); }); it("ignores cached successes outside the current retry selection", () => { expect( resolveOnboardingLandingProject( ["/projects/current"], new Map([["/projects/previous", "previous"]]), new Map([ ["/projects/previous", "previous"], ["/projects/current", "current"], ]), ), ).toBe("current"); });}); describe("projects on multiple computers", () => { it("keeps identical paths independent when selecting a landing project after partial imports", () => { const first = onboardingProjectKey(EnvironmentId.make("first"), "/code/app"); const second = onboardingProjectKey(EnvironmentId.make("second"), "/code/app"); const completed = new Map([[first, "first-project"]]); expect(completed.has(second)).toBe(false); expect(resolveOnboardingLandingProject([second], completed, completed)).toBeUndefined(); completed.set(second, "second-project"); expect(resolveOnboardingLandingProject([second, first], completed, completed)).toBe( "second-project", ); }); it("preserves computer identity when choosing recent projects", () => { const first = { ...candidate("/code/app"), environmentId: "first" }; const second = { ...candidate("/code/app"), environmentId: "second" }; expect(partitionOnboardingProjects([first, second], now).recent).toEqual([first, second]); });});
import { EnvironmentId, ProjectId, type AgentSessionProjectCandidate } from "@t3tools/contracts";
import { describe, expect, it } from "vite-plus/test";
import {
groupOnboardingProjects,
partitionOnboardingProjects,
onboardingProjectKey,
resolveOnboardingLandingProject,
resolveOnboardingProjectId,
} from "./projectImport.logic";
const now = Date.parse("2026-08-22T12:00:00.000Z");
function candidate(
path: string,
overrides: Partial<AgentSessionProjectCandidate> = {},
): AgentSessionProjectCandidate {
return {
title: path.split("/").at(-1) ?? path,
path,
sources: ["codex"],
threadCount: 3,
lastActiveAt: "2026-08-20T12:00:00.000Z",
alreadyImported: false,
git: { remoteKey: null, repository: null },
...overrides,
};
}
const github = (repository: string) => ({
remoteKey: `github.com/${repository.toLowerCase()}`,
repository,
});
describe("partitionOnboardingProjects", () => {
it("keeps existing projects available for thread history import", () => {
const imported = candidate("/projects/current", { alreadyImported: true });
const available = candidate("/projects/other");
expect(partitionOnboardingProjects([imported, available], now)).toEqual({
available: [imported, available],
recent: [imported, available],
});
});
it("keeps projects older than 30 days out of the default selection", () => {
const recent = candidate("/projects/recent");
const older = candidate("/projects/older", {
lastActiveAt: "2026-07-01T12:00:00.000Z",
});
expect(partitionOnboardingProjects([recent, older], now)).toEqual({
available: [recent, older],
recent: [recent],
});
});
it("keeps future activity out of the default selection", () => {
const recent = candidate("/projects/recent");
const future = candidate("/projects/future", {
lastActiveAt: "2026-08-23T12:00:00.000Z",
});
expect(partitionOnboardingProjects([recent, future], now)).toEqual({
available: [recent, future],
recent: [recent],
});
});
it("keeps non-git folders and thin histories out of the default selection", () => {
const repo = candidate("/projects/repo");
const folder = candidate("/projects/folder", { git: null });
const thin = candidate("/projects/thin", { threadCount: 2 });
expect(partitionOnboardingProjects([repo, folder, thin], now).recent).toEqual([repo]);
});
it("selects candidates from servers that do not report git identity", () => {
const { git: _git, ...legacy } = candidate("/projects/legacy");
expect(partitionOnboardingProjects([legacy], now).recent).toEqual([legacy]);
});
});
describe("groupOnboardingProjects", () => {
it("groups clones by origin, keeps local repos separate, and folds non-git folders away", () => {
const main = candidate("/code/t3code", {
git: github("pingdotgg/t3code"),
threadCount: 79,
lastActiveAt: "2026-08-21T12:00:00.000Z",
});
const clone = candidate("/code/clones/t3code-2", {
git: github("pingdotgg/t3code"),
threadCount: 13,
lastActiveAt: "2026-08-10T12:00:00.000Z",
});
const older = candidate("/code/fleet", {
git: github("t3dotgg/fleet"),
threadCount: 295,
lastActiveAt: "2026-08-22T00:00:00.000Z",
});
const local = candidate("/code/scratch-repo", { title: "scratch-repo" });
const folder = candidate("/tmp/notes", { git: null });
const grouped = groupOnboardingProjects([main, clone, older, local, folder]);
expect(grouped.other).toEqual([folder]);
expect(
grouped.repositories.map((group) => ({
label: group.label,
paths: group.candidates.map((item) => item.path),
threadCount: group.threadCount,
lastActiveAt: group.lastActiveAt,
})),
).toEqual([
{
label: "t3dotgg/fleet",
paths: ["/code/fleet"],
threadCount: 295,
lastActiveAt: "2026-08-22T00:00:00.000Z",
},
{
label: "pingdotgg/t3code",
paths: ["/code/t3code", "/code/clones/t3code-2"],
threadCount: 92,
lastActiveAt: "2026-08-21T12:00:00.000Z",
},
{
label: "scratch-repo",
paths: ["/code/scratch-repo"],
threadCount: 3,
lastActiveAt: "2026-08-20T12:00:00.000Z",
},
]);
});
it("lists candidates without git identity as standalone repositories", () => {
const { git: _git, ...legacy } = candidate("/code/legacy", { title: "legacy" });
const grouped = groupOnboardingProjects([legacy]);
expect(grouped.other).toEqual([]);
expect(grouped.repositories.map((group) => [group.label, group.repository])).toEqual([
["legacy", null],
]);
});
});
describe("resolveOnboardingProjectId", () => {
const localEnvironmentId = EnvironmentId.make("local");
const remoteEnvironmentId = EnvironmentId.make("remote");
const localProjectId = ProjectId.make("local-project");
it("uses the scanned project ID before the project reaches the client", () => {
expect(
resolveOnboardingProjectId(
[],
localEnvironmentId,
candidate("/projects/repo", { projectId: localProjectId }),
),
).toBe(localProjectId);
});
it("uses the scanned project ID when the client still has an older project at that root", () => {
expect(
resolveOnboardingProjectId(
[
{
id: ProjectId.make("stale-project"),
environmentId: localEnvironmentId,
workspaceRoot: "/projects/repo",
},
],
localEnvironmentId,
candidate("/projects/repo", { projectId: localProjectId }),
),
).toBe(localProjectId);
});
it("returns null to create a project when neither the scan nor the client has a project ID", () => {
expect(
resolveOnboardingProjectId([], localEnvironmentId, candidate("/projects/new")),
).toBeNull();
});
it("finds an existing project by normalized root in the target environment", () => {
expect(
resolveOnboardingProjectId(
[
{
id: ProjectId.make("remote-project"),
environmentId: remoteEnvironmentId,
workspaceRoot: "C:\\Work\\Repo",
},
{
id: localProjectId,
environmentId: localEnvironmentId,
workspaceRoot: "C:\\Work\\Repo\\",
},
],
localEnvironmentId,
candidate("c:/work/repo"),
),
).toBe(localProjectId);
});
it("does not reuse a project from another environment", () => {
expect(
resolveOnboardingProjectId(
[
{
id: ProjectId.make("remote-project"),
environmentId: remoteEnvironmentId,
workspaceRoot: "/projects/repo",
},
],
localEnvironmentId,
candidate("/projects/repo"),
),
).toBeNull();
});
it("finds an alias after the scanner returns its persisted project root", () => {
expect(
resolveOnboardingProjectId(
[
{
id: localProjectId,
environmentId: localEnvironmentId,
workspaceRoot: "/real/projects/repo",
},
],
localEnvironmentId,
candidate("/real/projects/repo"),
),
).toBe(localProjectId);
});
it("finds the current root owner when the scan has no project ID", () => {
const recreatedProjectId = ProjectId.make("recreated-project");
expect(
resolveOnboardingProjectId(
[
{
id: localProjectId,
environmentId: localEnvironmentId,
workspaceRoot: "/projects/other",
},
{
id: recreatedProjectId,
environmentId: localEnvironmentId,
workspaceRoot: "/projects/repo",
},
],
localEnvironmentId,
candidate("/projects/repo"),
),
).toBe(recreatedProjectId);
});
it("does not reuse a moved project when the scan has no project ID", () => {
expect(
resolveOnboardingProjectId(
[
{
id: localProjectId,
environmentId: localEnvironmentId,
workspaceRoot: "/projects/moved",
},
],
localEnvironmentId,
candidate("/projects/repo"),
),
).toBeNull();
});
});
describe("resolveOnboardingLandingProject", () => {
it("skips a failed first project for a later project with imported history", () => {
expect(
resolveOnboardingLandingProject(
["/projects/failed", "/projects/imported"],
new Map([["/projects/imported", "imported"]]),
new Map([["/projects/imported", "imported"]]),
),
).toBe("imported");
});
it("prefers a partial first import that added history", () => {
expect(
resolveOnboardingLandingProject(
["/projects/partial", "/projects/complete"],
new Map([["/projects/partial", "partial"]]),
new Map([["/projects/complete", "complete"]]),
),
).toBe("partial");
});
it("uses a completed zero-history project when no import added history", () => {
expect(
resolveOnboardingLandingProject(
["/projects/empty", "/projects/failed"],
new Map(),
new Map([["/projects/empty", "empty"]]),
),
).toBe("empty");
});
it("keeps an earlier successful import available on retry", () => {
expect(
resolveOnboardingLandingProject(
["/projects/imported", "/projects/retry"],
new Map([["/projects/imported", "imported"]]),
new Map([["/projects/imported", "imported"]]),
),
).toBe("imported");
});
it("ignores cached successes outside the current retry selection", () => {
expect(
resolveOnboardingLandingProject(
["/projects/current"],
new Map([["/projects/previous", "previous"]]),
new Map([
["/projects/previous", "previous"],
["/projects/current", "current"],
]),
),
).toBe("current");
});
});
describe("projects on multiple computers", () => {
it("keeps identical paths independent when selecting a landing project after partial imports", () => {
const first = onboardingProjectKey(EnvironmentId.make("first"), "/code/app");
const second = onboardingProjectKey(EnvironmentId.make("second"), "/code/app");
const completed = new Map([[first, "first-project"]]);
expect(completed.has(second)).toBe(false);
expect(resolveOnboardingLandingProject([second], completed, completed)).toBeUndefined();
completed.set(second, "second-project");
expect(resolveOnboardingLandingProject([second, first], completed, completed)).toBe(
"second-project",
);
});
it("preserves computer identity when choosing recent projects", () => {
const first = { ...candidate("/code/app"), environmentId: "first" };
const second = { ...candidate("/code/app"), environmentId: "second" };
expect(partitionOnboardingProjects([first, second], now).recent).toEqual([first, second]);
});
});