# Files changed · PR \#10578 · pingdotgg/t3code

[View on GitCafe](https://git.cafe/pingdotgg/t3code/pull/10578/diffs)

Repository: [pingdotgg/t3code](https://git.cafe/pingdotgg/t3code)

Visibility: public

[refactor\(project\): share create and update inputs](https://git.cafe/pingdotgg/t3code/pull/10578.md)

Version: 2

Head commit: 80736156baa52edd357468cb49a39fd3782d4ad0

Base commit: a645d4e33015b8657503ca7f978ab56b2d106f93

Comparison base: a645d4e33015b8657503ca7f978ab56b2d106f93

Showing up to 20 entries from this revision. If the pull request changes, reopen the first page before continuing.

## apps/server/src/project/ProjectService.ts

Status: modified; +4; −19

```diff
@@ -1,12 +1,10 @@
 import {
   CommandId,
-  ModelSelection,
   ProjectId,
   type Project,
-  type ProjectIconOverride,
-  type ProjectScript,
+  type ProjectCreatePayload,
+  type ProjectUpdatePayload,
   type ProjectSnapshot,
-  type ThreadEnvMode,
 } from "@t3tools/contracts";
 import * as Context from "effect/Context";
 import * as DateTime from "effect/DateTime";
@@ -29,27 +27,14 @@
 import { ProjectEnrichmentService, type ProjectEnrichment } from "./ProjectEnrichmentService.ts";
 import * as WorkspacePaths from "../workspace/WorkspacePaths.ts";
 
-export interface ProjectCreateInput {
+export interface ProjectCreateInput extends ProjectCreatePayload {
   readonly commandId: CommandId;
   readonly projectId: ProjectId;
-  readonly title: string;
-  readonly workspaceRoot: string;
-  readonly createWorkspaceRootIfMissing?: boolean;
-  readonly defaultModelSelection?: ModelSelection | null;
-  readonly scripts?: ReadonlyArray<ProjectScript>;
 }
 
+export interface ProjectUpdateInput extends ProjectUpdatePayload {
-export interface ProjectUpdateInput {
   readonly commandId: CommandId;
   readonly projectId: ProjectId;
-  readonly title?: string;
-  readonly workspaceRoot?: string;
-  readonly defaultModelSelection?: ModelSelection | null;
-  readonly autoPull?: boolean;
-  readonly projectIcon?: ProjectIconOverride | null;
-  readonly faviconPath?: string | null;
-  readonly defaultThreadEnvMode?: ThreadEnvMode | null;
-  readonly scripts?: ReadonlyArray<ProjectScript>;
 }
 
 export interface ProjectBootstrapInput extends ProjectCreateInput {}
```

## packages/contracts/src/project.test.ts

Status: modified; +42; −0

```diff
@@ -3,6 +3,9 @@
 
 import {
   ProjectReadFileError,
+  ProjectCreatePayload,
+  ProjectUpdatePayload,
+  ProjectMutation,
   ProjectSearchContentsError,
   ProjectSearchContentsInput,
   ProjectSearchEntriesError,
@@ -10,6 +13,9 @@
   ProjectWriteFileError,
 } from "./project.ts";
 
+const decodeProjectCreatePayload = Schema.decodeUnknownSync(ProjectCreatePayload);
+const decodeProjectUpdatePayload = Schema.decodeUnknownSync(ProjectUpdatePayload);
+const decodeProjectMutation = Schema.decodeUnknownSync(ProjectMutation);
 const decodeSearchEntriesInput = Schema.decodeUnknownSync(ProjectSearchEntriesInput);
 const decodeSearchContentsInput = Schema.decodeUnknownSync(ProjectSearchContentsInput);
 
@@ -107,3 +113,39 @@
     expect(writeError.failure).toBeUndefined();
   });
 });
+
+describe("shared project payloads", () => {
+  it("preserves omitted, false, and null values through RPC envelopes", () => {
+    const create = decodeProjectCreatePayload({
+      title: " Example ",
+      workspaceRoot: "/workspace",
+      createWorkspaceRootIfMissing: false,
+    });
+    const update = decodeProjectUpdatePayload({
+      autoPull: false,
+      defaultModelSelection: null,
+      faviconPath: null,
+    });
+    const envelope = { commandId: "command", projectId: "project" };
+    expect(decodeProjectMutation({ type: "project.create", ...envelope, ...create })).toEqual({
+      type: "project.create",
+      ...envelope,
+      title: "Example",
+      workspaceRoot: "/workspace",
+      createWorkspaceRootIfMissing: false,
+    });
+    expect(decodeProjectMutation({ type: "project.update", ...envelope, ...update })).toEqual({
+      type: "project.update",
+      ...envelope,
+      autoPull: false,
+      defaultModelSelection: null,
+      faviconPath: null,
+    });
+    expect(Object.hasOwn(create, "scripts")).toBe(false);
+    expect(Object.hasOwn(update, "title")).toBe(false);
+    // Internal RPC callers may explicitly supply undefined, as before the extraction.
+    expect(
+      decodeProjectMutation({ type: "project.update", ...envelope, title: undefined }),
+    ).toHaveProperty("title", undefined);
+  });
+});
```

## packages/contracts/src/project.ts

Status: modified; +23; −13

```diff
@@ -112,30 +112,40 @@
   }),
 ]);
 export type ProjectChange = typeof ProjectChange.Type;
+
+export const ProjectCreatePayload = Schema.Struct({
+  title: TrimmedNonEmptyString,
+  workspaceRoot: TrimmedNonEmptyString,
+  createWorkspaceRootIfMissing: Schema.optional(Schema.Boolean),
+  defaultModelSelection: Schema.optional(Schema.NullOr(ModelSelection)),
+  scripts: Schema.optional(Schema.Array(ProjectScript)),
+});
+export type ProjectCreatePayload = typeof ProjectCreatePayload.Type;
 
+export const ProjectUpdatePayload = Schema.Struct({
+  title: Schema.optional(TrimmedNonEmptyString),
+  workspaceRoot: Schema.optional(TrimmedNonEmptyString),
+  defaultModelSelection: Schema.optional(Schema.NullOr(ModelSelection)),
+  autoPull: Schema.optional(Schema.Boolean),
+  projectIcon: Schema.optional(Schema.NullOr(ProjectIconOverride)),
+  faviconPath: Schema.optional(Schema.NullOr(TrimmedNonEmptyString)),
+  defaultThreadEnvMode: Schema.optional(Schema.NullOr(ThreadEnvMode)),
+  scripts: Schema.optional(Schema.Array(ProjectScript)),
+});
+export type ProjectUpdatePayload = typeof ProjectUpdatePayload.Type;
+
 export const ProjectMutation = Schema.Union([
   Schema.Struct({
     type: Schema.Literal("project.create"),
     commandId: CommandId,
     projectId: ProjectId,
-    title: TrimmedNonEmptyString,
+    ...ProjectCreatePayload.fields,
-    workspaceRoot: TrimmedNonEmptyString,
-    createWorkspaceRootIfMissing: Schema.optional(Schema.Boolean),
-    defaultModelSelection: Schema.optional(Schema.NullOr(ModelSelection)),
-    scripts: Schema.optional(Schema.Array(ProjectScript)),
   }),
   Schema.Struct({
     type: Schema.Literal("project.update"),
     commandId: CommandId,
     projectId: ProjectId,
-    title: Schema.optional(TrimmedNonEmptyString),
+    ...ProjectUpdatePayload.fields,
-    workspaceRoot: Schema.optional(TrimmedNonEmptyString),
-    defaultModelSelection: Schema.optional(Schema.NullOr(ModelSelection)),
-    autoPull: Schema.optional(Schema.Boolean),
-    projectIcon: Schema.optional(Schema.NullOr(ProjectIconOverride)),
-    faviconPath: Schema.optional(Schema.NullOr(TrimmedNonEmptyString)),
-    defaultThreadEnvMode: Schema.optional(Schema.NullOr(ThreadEnvMode)),
-    scripts: Schema.optional(Schema.Array(ProjectScript)),
   }),
   Schema.Struct({
     type: Schema.Literal("project.delete"),
```
