import type { EnvironmentProject } from "@t3tools/client-runtime/state/shell";import type { EnvironmentId } from "@t3tools/contracts"; import { scopedProjectKey } from "../../lib/scopedEntities";import type { HomeProjectScope } from "../home/homeThreadList"; type DraftProjectSelectionResolution = | { readonly kind: "preserve" } | { readonly kind: "select"; readonly project: EnvironmentProject } | { readonly kind: "pick" }; export function getProjectScopeSelectionTarget( scope: HomeProjectScope, preferredEnvironmentId: EnvironmentId | null,): EnvironmentProject { return ( scope.projects.find((project) => project.environmentId === preferredEnvironmentId) ?? scope.representative );} function getOnlySelectableProject( projectScopes: ReadonlyArray<HomeProjectScope>,): EnvironmentProject | null { const onlyScope = projectScopes.length === 1 ? projectScopes[0] : null; return onlyScope?.representative ?? null;} /** * Picks the project on a target environment that corresponds to the project * currently selected in the new-task flow, so switching computers follows the * same repo. Repository identity is preferred; projects without one (e.g. not * yet indexed) fall back to workspace basename, then title. When nothing * matches, the first project on the target stands in — the same fallback the * render path applies when no key is selected — so the draft always has a * concrete key to carry over to. */export function resolveEnvironmentProjectMatch( projectsOnTarget: ReadonlyArray<EnvironmentProject>, selectedProject: EnvironmentProject | null,): EnvironmentProject | null { const repositoryKey = selectedProject?.repositoryIdentity?.canonicalKey ?? null; // `|| null` (not `??`): a pending-task placeholder project can have an empty // workspaceRoot, and an "" basename would match nothing meaningful. const workspaceBasename = selectedProject?.workspaceRoot.split("/").at(-1) || null; // The weaker signals only apply where identity is unknown on at least one // side; two known, different repositories never match on a shared basename // or title (mirrors the environment list filter in the new-task flow). const isKnownMismatch = (project: EnvironmentProject) => { const projectKey = project.repositoryIdentity?.canonicalKey ?? null; return repositoryKey !== null && projectKey !== null && projectKey !== repositoryKey; }; return ( (repositoryKey !== null ? projectsOnTarget.find( (project) => (project.repositoryIdentity?.canonicalKey ?? null) === repositoryKey, ) : undefined) ?? (workspaceBasename !== null ? projectsOnTarget.find( (project) => !isKnownMismatch(project) && project.workspaceRoot.split("/").at(-1) === workspaceBasename, ) : undefined) ?? (selectedProject !== null ? projectsOnTarget.find( (project) => !isKnownMismatch(project) && project.title === selectedProject.title, ) : undefined) ?? projectsOnTarget[0] ?? null );} export function resolveDraftProjectSelection( selectedProjectKey: string | null, projects: ReadonlyArray<EnvironmentProject>, projectScopes: ReadonlyArray<HomeProjectScope>,): DraftProjectSelectionResolution { const hasExplicitProjectSelection = selectedProjectKey !== null && projects.some( (project) => scopedProjectKey(project.environmentId, project.id) === selectedProjectKey, ); if (hasExplicitProjectSelection) { return { kind: "preserve" }; } const onlyProject = getOnlySelectableProject(projectScopes); return onlyProject ? { kind: "select", project: onlyProject } : { kind: "pick" };}
import type { EnvironmentProject } from "@t3tools/client-runtime/state/shell";
import type { EnvironmentId } from "@t3tools/contracts";
import { scopedProjectKey } from "../../lib/scopedEntities";
import type { HomeProjectScope } from "../home/homeThreadList";
type DraftProjectSelectionResolution =
| { readonly kind: "preserve" }
| { readonly kind: "select"; readonly project: EnvironmentProject }
| { readonly kind: "pick" };
export function getProjectScopeSelectionTarget(
scope: HomeProjectScope,
preferredEnvironmentId: EnvironmentId | null,
): EnvironmentProject {
return (
scope.projects.find((project) => project.environmentId === preferredEnvironmentId) ??
scope.representative
);
}
function getOnlySelectableProject(
projectScopes: ReadonlyArray<HomeProjectScope>,
): EnvironmentProject | null {
const onlyScope = projectScopes.length === 1 ? projectScopes[0] : null;
return onlyScope?.representative ?? null;
}
/**
* Picks the project on a target environment that corresponds to the project
* currently selected in the new-task flow, so switching computers follows the
* same repo. Repository identity is preferred; projects without one (e.g. not
* yet indexed) fall back to workspace basename, then title. When nothing
* matches, the first project on the target stands in — the same fallback the
* render path applies when no key is selected — so the draft always has a
* concrete key to carry over to.
*/
export function resolveEnvironmentProjectMatch(
projectsOnTarget: ReadonlyArray<EnvironmentProject>,
selectedProject: EnvironmentProject | null,
): EnvironmentProject | null {
const repositoryKey = selectedProject?.repositoryIdentity?.canonicalKey ?? null;
// `|| null` (not `??`): a pending-task placeholder project can have an empty
// workspaceRoot, and an "" basename would match nothing meaningful.
const workspaceBasename = selectedProject?.workspaceRoot.split("/").at(-1) || null;
// The weaker signals only apply where identity is unknown on at least one
// side; two known, different repositories never match on a shared basename
// or title (mirrors the environment list filter in the new-task flow).
const isKnownMismatch = (project: EnvironmentProject) => {
const projectKey = project.repositoryIdentity?.canonicalKey ?? null;
return repositoryKey !== null && projectKey !== null && projectKey !== repositoryKey;
};
return (
(repositoryKey !== null
? projectsOnTarget.find(
(project) => (project.repositoryIdentity?.canonicalKey ?? null) === repositoryKey,
)
: undefined) ??
(workspaceBasename !== null
? projectsOnTarget.find(
(project) =>
!isKnownMismatch(project) &&
project.workspaceRoot.split("/").at(-1) === workspaceBasename,
)
: undefined) ??
(selectedProject !== null
? projectsOnTarget.find(
(project) => !isKnownMismatch(project) && project.title === selectedProject.title,
)
: undefined) ??
projectsOnTarget[0] ??
null
);
}
export function resolveDraftProjectSelection(
selectedProjectKey: string | null,
projects: ReadonlyArray<EnvironmentProject>,
projectScopes: ReadonlyArray<HomeProjectScope>,
): DraftProjectSelectionResolution {
const hasExplicitProjectSelection =
selectedProjectKey !== null &&
projects.some(
(project) => scopedProjectKey(project.environmentId, project.id) === selectedProjectKey,
);
if (hasExplicitProjectSelection) {
return { kind: "preserve" };
}
const onlyProject = getOnlySelectableProject(projectScopes);
return onlyProject ? { kind: "select", project: onlyProject } : { kind: "pick" };
}