authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-06-20 11:06:38-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-06-20 11:06:38-07:00
log4fbc4f9b20cc4d2e991349ce30b0118473de4621
treebae9892d21fbb9b58e63bb263db1cba1ca7d7e0f
parent779c2374876884992c7cdf059556acd255db824e
signature Signed by PGP key B5690EEEBB952194

[codex] Structure mobile project thread validation errors (#3387)

Co-authored-by: codex <codex@users.noreply.github.com>

2 files changed, 67 insertions(+), 9 deletions(-)

apps/mobile/src/features/threads/projectThreadCreationValidation.ts created+56
...@@ -0,0 +1,56 @@
1import { EnvironmentId, ProjectId } from "@t3tools/contracts";
2import * as Schema from "effect/Schema";
3
4export class ProjectThreadTaskRequiredError extends Schema.TaggedErrorClass<ProjectThreadTaskRequiredError>()(
5 "ProjectThreadTaskRequiredError",
6 {
7 environmentId: EnvironmentId,
8 projectId: ProjectId,
9 environmentMode: Schema.Literals(["local", "worktree"]),
10 },
11) {
12 override get message(): string {
13 return "Enter a task before starting the thread.";
14 }
15}
16
17export class ProjectThreadBaseBranchRequiredError extends Schema.TaggedErrorClass<ProjectThreadBaseBranchRequiredError>()(
18 "ProjectThreadBaseBranchRequiredError",
19 {
20 environmentId: EnvironmentId,
21 projectId: ProjectId,
22 },
23) {
24 override get message(): string {
25 return "Select a base branch before creating a worktree.";
26 }
27}
28
29export const ProjectThreadCreationValidationError = Schema.Union([
30 ProjectThreadTaskRequiredError,
31 ProjectThreadBaseBranchRequiredError,
32]);
33export type ProjectThreadCreationValidationError = typeof ProjectThreadCreationValidationError.Type;
34
35export function validateProjectThreadCreation(input: {
36 readonly environmentId: EnvironmentId;
37 readonly projectId: ProjectId;
38 readonly environmentMode: "local" | "worktree";
39 readonly branch: string | null;
40 readonly initialMessageText: string;
41}): ProjectThreadCreationValidationError | null {
42 if (input.initialMessageText.trim().length === 0) {
43 return new ProjectThreadTaskRequiredError({
44 environmentId: input.environmentId,
45 projectId: input.projectId,
46 environmentMode: input.environmentMode,
47 });
48 }
49 if (input.environmentMode === "worktree" && !input.branch) {
50 return new ProjectThreadBaseBranchRequiredError({
51 environmentId: input.environmentId,
52 projectId: input.projectId,
53 });
54 }
55 return null;
56}
apps/mobile/src/features/threads/use-project-actions.ts+11-9
...@@ -21,6 +21,7 @@ import { makeTurnCommandMetadata } from "../../lib/commandMetadata";...@@ -21,6 +21,7 @@ import { makeTurnCommandMetadata } from "../../lib/commandMetadata";
21import { uuidv4 } from "../../lib/uuid";21import { uuidv4 } from "../../lib/uuid";
22import { useAtomCommand } from "../../state/use-atom-command";22import { useAtomCommand } from "../../state/use-atom-command";
23import { setPendingConnectionError } from "../../state/use-remote-environment-registry";23import { setPendingConnectionError } from "../../state/use-remote-environment-registry";
24import { validateProjectThreadCreation } from "./projectThreadCreationValidation";
2425
25function deriveThreadTitleFromPrompt(value: string): string {26function deriveThreadTitleFromPrompt(value: string): string {
26 const trimmed = value.trim();27 const trimmed = value.trim();
...@@ -52,15 +53,16 @@ export function useCreateProjectThread() {...@@ -52,15 +53,16 @@ export function useCreateProjectThread() {
52 const initialMessageText = input.initialMessageText.trim();53 const initialMessageText = input.initialMessageText.trim();
53 const nextTitle = deriveThreadTitleFromPrompt(input.initialMessageText);54 const nextTitle = deriveThreadTitleFromPrompt(input.initialMessageText);
5455
55 if (initialMessageText.length === 0) {56 const validationError = validateProjectThreadCreation({
56 const error = new Error("Enter a task before starting the thread.");57 environmentId: input.project.environmentId,
57 setPendingConnectionError(error.message);58 projectId: input.project.id,
58 return AsyncResult.failure(Cause.fail(error));59 environmentMode: input.envMode,
59 }60 branch: input.branch,
60 if (input.envMode === "worktree" && !input.branch) {61 initialMessageText,
61 const error = new Error("Select a base branch before creating a worktree.");62 });
62 setPendingConnectionError(error.message);63 if (validationError !== null) {
63 return AsyncResult.failure(Cause.fail(error));64 setPendingConnectionError(validationError.message);
65 return AsyncResult.failure(Cause.fail(validationError));
64 }66 }
6567
66 const isWorktree = input.envMode === "worktree";68 const isWorktree = input.envMode === "worktree";