import { EnvironmentId, ProjectId } from "@t3tools/contracts";import * as Schema from "effect/Schema";export class ProjectThreadTaskRequiredError extends Schema.TaggedError<ProjectThreadTaskRequiredError>()( "ProjectThreadTaskRequiredError", { environmentId: EnvironmentId, projectId: ProjectId, environmentMode: Schema.Literals(["local", "worktree"]), },) { override get message(): string { return "Enter a task before starting the thread."; }}export class ProjectThreadBaseBranchRequiredError extends Schema.TaggedError<ProjectThreadBaseBranchRequiredError>()( "ProjectThreadBaseBranchRequiredError", { environmentId: EnvironmentId, projectId: ProjectId, },) { override get message(): string { return "Select a base branch before creating a worktree."; }}export const ProjectThreadCreationValidationError = Schema.Union([ ProjectThreadTaskRequiredError, ProjectThreadBaseBranchRequiredError,]);export type ProjectThreadCreationValidationError = typeof ProjectThreadCreationValidationError.Type;/** * Branch recorded on a thread created from the new-task composer. An explicit * picker choice always wins. An untouched current-checkout draft records the * ref that is actually checked out, so the thread's PR badge and branch label * match the live git status instead of staying blank. A detached HEAD, a * non-repository project, or a status that has not arrived stays null rather * than fabricating a branch. Only the online creation path resolves a * checkout; a queued task cannot know the checkout it will drain against. */export function resolveProjectThreadCreationBranch(input: { readonly workspaceMode: "local" | "worktree"; readonly selectedBranch: string | null; readonly currentCheckoutBranch: string | null;}): string | null { if (input.selectedBranch !== null) { return input.selectedBranch; } return input.workspaceMode === "local" ? input.currentCheckoutBranch : null;}export function validateProjectThreadCreation(input: { readonly environmentId: EnvironmentId; readonly projectId: ProjectId; readonly environmentMode: "local" | "worktree"; readonly branch: string | null; readonly initialMessageText: string;}): ProjectThreadCreationValidationError | null { if (input.initialMessageText.trim().length === 0) { return new ProjectThreadTaskRequiredError({ environmentId: input.environmentId, projectId: input.projectId, environmentMode: input.environmentMode, }); } if (input.environmentMode === "worktree" && !input.branch) { return new ProjectThreadBaseBranchRequiredError({ environmentId: input.environmentId, projectId: input.projectId, }); } return null;}
import { EnvironmentId, ProjectId } from "@t3tools/contracts";
import * as Schema from "effect/Schema";
export class ProjectThreadTaskRequiredError extends Schema.TaggedError<ProjectThreadTaskRequiredError>()(
"ProjectThreadTaskRequiredError",
{
environmentId: EnvironmentId,
projectId: ProjectId,
environmentMode: Schema.Literals(["local", "worktree"]),
},
) {
override get message(): string {
return "Enter a task before starting the thread.";
}
}
export class ProjectThreadBaseBranchRequiredError extends Schema.TaggedError<ProjectThreadBaseBranchRequiredError>()(
"ProjectThreadBaseBranchRequiredError",
{
environmentId: EnvironmentId,
projectId: ProjectId,
},
) {
override get message(): string {
return "Select a base branch before creating a worktree.";
}
}
export const ProjectThreadCreationValidationError = Schema.Union([
ProjectThreadTaskRequiredError,
ProjectThreadBaseBranchRequiredError,
]);
export type ProjectThreadCreationValidationError = typeof ProjectThreadCreationValidationError.Type;
/**
* Branch recorded on a thread created from the new-task composer. An explicit
* picker choice always wins. An untouched current-checkout draft records the
* ref that is actually checked out, so the thread's PR badge and branch label
* match the live git status instead of staying blank. A detached HEAD, a
* non-repository project, or a status that has not arrived stays null rather
* than fabricating a branch. Only the online creation path resolves a
* checkout; a queued task cannot know the checkout it will drain against.
*/
export function resolveProjectThreadCreationBranch(input: {
readonly workspaceMode: "local" | "worktree";
readonly selectedBranch: string | null;
readonly currentCheckoutBranch: string | null;
}): string | null {
if (input.selectedBranch !== null) {
return input.selectedBranch;
}
return input.workspaceMode === "local" ? input.currentCheckoutBranch : null;
}
export function validateProjectThreadCreation(input: {
readonly environmentId: EnvironmentId;
readonly projectId: ProjectId;
readonly environmentMode: "local" | "worktree";
readonly branch: string | null;
readonly initialMessageText: string;
}): ProjectThreadCreationValidationError | null {
if (input.initialMessageText.trim().length === 0) {
return new ProjectThreadTaskRequiredError({
environmentId: input.environmentId,
projectId: input.projectId,
environmentMode: input.environmentMode,
});
}
if (input.environmentMode === "worktree" && !input.branch) {
return new ProjectThreadBaseBranchRequiredError({
environmentId: input.environmentId,
projectId: input.projectId,
});
}
return null;
}