| author | |
| committer | |
| log | 2afe87ea4bb94f4dbbd1660a556cbffefd7498a2 |
| tree | 86e3f4e85d1a4b16f50c39d600f07643e638033b |
| parent | cbb731beb36db2330c9a2632b11354315658ea21 |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>9 files changed, 59 insertions(+), 53 deletions(-)
apps/mobile/src/features/settings/components/BranchNamingSettings.tsx+1-1| ... | @@ -48,7 +48,7 @@ export function BranchNamingSettings(props: { | ... | @@ -48,7 +48,7 @@ export function BranchNamingSettings(props: { |
| 48 | {props.mode === "static" ? ( | 48 | {props.mode === "static" ? ( |
| 49 | <View className="gap-2 px-4 py-3"> | 49 | <View className="gap-2 px-4 py-3"> |
| 50 | <Text className="text-sm text-foreground-muted"> | 50 | <Text className="text-sm text-foreground-muted"> |
| 51 | Use t3code or t3code/ for t3code/add-search. Leave empty for no prefix. | 51 | Use t3 or t3/ for t3/add-search. Leave empty for no prefix. |
| 52 | </Text> | 52 | </Text> |
| 53 | <AppTextInput | 53 | <AppTextInput |
| 54 | key={props.prefix} | 54 | key={props.prefix} |
apps/server/src/orchestration-v2/ThreadLaunchService.test.ts+18-18| ... | @@ -1104,7 +1104,7 @@ it.effect("names the worktree itself when the client provides no branch", () => | ... | @@ -1104,7 +1104,7 @@ it.effect("names the worktree itself when the client provides no branch", () => |
| 1104 | yield* waitUntil(() => Effect.sync(() => harness.createWorktree.mock.calls.length === 1)); | 1104 | yield* waitUntil(() => Effect.sync(() => harness.createWorktree.mock.calls.length === 1)); |
| 1105 | assert.match( | 1105 | assert.match( |
| 1106 | harness.createWorktree.mock.calls[0]?.[0]?.newRefName ?? "", | 1106 | harness.createWorktree.mock.calls[0]?.[0]?.newRefName ?? "", |
| 1107 | /^t3code\/[0-9a-f]{8}$/u, | 1107 | /^t3\/[0-9a-f]{8}$/u, |
| 1108 | ); | 1108 | ); |
| 1109 | yield* waitUntil(() => | 1109 | yield* waitUntil(() => |
| 1110 | threads | 1110 | threads |
| ... | @@ -1115,7 +1115,7 @@ it.effect("names the worktree itself when the client provides no branch", () => | ... | @@ -1115,7 +1115,7 @@ it.effect("names the worktree itself when the client provides no branch", () => |
| 1115 | }), | 1115 | }), |
| 1116 | ); | 1116 | ); |
| 1117 | 1117 | ||
| 1118 | it.effect("renames a temporary t3code/<hash> branch off the provisioning critical path", () => | 1118 | it.effect("renames a temporary t3/<hash> branch off the provisioning critical path", () => |
| 1119 | Effect.gen(function* () { | 1119 | Effect.gen(function* () { |
| 1120 | const branchNameStarted = yield* Deferred.make<void>(); | 1120 | const branchNameStarted = yield* Deferred.make<void>(); |
| 1121 | const allowBranchName = yield* Deferred.make<void>(); | 1121 | const allowBranchName = yield* Deferred.make<void>(); |
| ... | @@ -1138,11 +1138,11 @@ it.effect("renames a temporary t3code/<hash> branch off the provisioning critica | ... | @@ -1138,11 +1138,11 @@ it.effect("renames a temporary t3code/<hash> branch off the provisioning critica |
| 1138 | command: "command:launch:temp-branch", | 1138 | command: "command:launch:temp-branch", |
| 1139 | thread: "thread:launch:temp-branch", | 1139 | thread: "thread:launch:temp-branch", |
| 1140 | message: "Build the feature", | 1140 | message: "Build the feature", |
| 1141 | workspace: { type: "worktree", baseRef: "main", branch: "t3code/abcd1234" }, | 1141 | workspace: { type: "worktree", baseRef: "main", branch: "t3/abcd1234" }, |
| 1142 | }), | 1142 | }), |
| 1143 | ); | 1143 | ); |
| 1144 | yield* Deferred.await(branchNameStarted); | 1144 | yield* Deferred.await(branchNameStarted); |
| 1145 | assert.equal(harness.createWorktree.mock.calls[0]?.[0]?.newRefName, "t3code/abcd1234"); | 1145 | assert.equal(harness.createWorktree.mock.calls[0]?.[0]?.newRefName, "t3/abcd1234"); |
| 1146 | yield* waitUntil(() => | 1146 | yield* waitUntil(() => |
| 1147 | threads | 1147 | threads |
| 1148 | .getThreadProjection(launched.threadId) | 1148 | .getThreadProjection(launched.threadId) |
| ... | @@ -1150,7 +1150,7 @@ it.effect("renames a temporary t3code/<hash> branch off the provisioning critica | ... | @@ -1150,7 +1150,7 @@ it.effect("renames a temporary t3code/<hash> branch off the provisioning critica |
| 1150 | ); | 1150 | ); |
| 1151 | assert.equal( | 1151 | assert.equal( |
| 1152 | (yield* threads.getThreadProjection(launched.threadId)).thread.branch, | 1152 | (yield* threads.getThreadProjection(launched.threadId)).thread.branch, |
| 1153 | "t3code/abcd1234", | 1153 | "t3/abcd1234", |
| 1154 | ); | 1154 | ); |
| 1155 | yield* Deferred.succeed(allowBranchName, undefined); | 1155 | yield* Deferred.succeed(allowBranchName, undefined); |
| 1156 | yield* waitUntil(() => | 1156 | yield* waitUntil(() => |
| ... | @@ -1160,17 +1160,17 @@ it.effect("renames a temporary t3code/<hash> branch off the provisioning critica | ... | @@ -1160,17 +1160,17 @@ it.effect("renames a temporary t3code/<hash> branch off the provisioning critica |
| 1160 | ); | 1160 | ); |
| 1161 | assert.deepEqual(harness.renameBranch.mock.calls[0]?.[0], { | 1161 | assert.deepEqual(harness.renameBranch.mock.calls[0]?.[0], { |
| 1162 | cwd: "/repo-worktrees/temp", | 1162 | cwd: "/repo-worktrees/temp", |
| 1163 | oldBranch: "t3code/abcd1234", | 1163 | oldBranch: "t3/abcd1234", |
| 1164 | newBranch: "generated-branch", | 1164 | newBranch: "generated-branch", |
| 1165 | }); | 1165 | }); |
| 1166 | }).pipe(Effect.provide(harness.layer)); | 1166 | }).pipe(Effect.provide(harness.layer)); |
| 1167 | }), | 1167 | }), |
| 1168 | ); | 1168 | ); |
| 1169 | 1169 | ||
| 1170 | it.effect("provisions under t3code-<hash> when a plain t3code branch blocks t3code/*", () => | 1170 | it.effect("provisions under t3-<hash> when a plain t3 branch blocks t3/*", () => |
| 1171 | Effect.gen(function* () { | 1171 | Effect.gen(function* () { |
| 1172 | const harness = makeHarness({ | 1172 | const harness = makeHarness({ |
| 1173 | hasCommit: (input) => Effect.succeed(input.refName === "refs/heads/t3code"), | 1173 | hasCommit: (input) => Effect.succeed(input.refName === "refs/heads/t3"), |
| 1174 | createWorktree: (input) => | 1174 | createWorktree: (input) => |
| 1175 | Effect.succeed({ | 1175 | Effect.succeed({ |
| 1176 | worktree: { path: "/repo-worktrees/temp", refName: input.newRefName, headSha: "abc" }, | 1176 | worktree: { path: "/repo-worktrees/temp", refName: input.newRefName, headSha: "abc" }, |
| ... | @@ -1184,7 +1184,7 @@ it.effect("provisions under t3code-<hash> when a plain t3code branch blocks t3co | ... | @@ -1184,7 +1184,7 @@ it.effect("provisions under t3code-<hash> when a plain t3code branch blocks t3co |
| 1184 | command: "command:launch:blocked-namespace", | 1184 | command: "command:launch:blocked-namespace", |
| 1185 | thread: "thread:launch:blocked-namespace", | 1185 | thread: "thread:launch:blocked-namespace", |
| 1186 | message: "Build the feature", | 1186 | message: "Build the feature", |
| 1187 | workspace: { type: "worktree", baseRef: "main", branch: "t3code/abcd1234" }, | 1187 | workspace: { type: "worktree", baseRef: "main", branch: "t3/abcd1234" }, |
| 1188 | }), | 1188 | }), |
| 1189 | ); | 1189 | ); |
| 1190 | yield* waitUntil(() => | 1190 | yield* waitUntil(() => |
| ... | @@ -1192,8 +1192,8 @@ it.effect("provisions under t3code-<hash> when a plain t3code branch blocks t3co | ... | @@ -1192,8 +1192,8 @@ it.effect("provisions under t3code-<hash> when a plain t3code branch blocks t3co |
| 1192 | .getThreadProjection(launched.threadId) | 1192 | .getThreadProjection(launched.threadId) |
| 1193 | .pipe(Effect.map((projection) => projection.thread.branch === "generated-branch")), | 1193 | .pipe(Effect.map((projection) => projection.thread.branch === "generated-branch")), |
| 1194 | ); | 1194 | ); |
| 1195 | assert.equal(harness.createWorktree.mock.calls[0]?.[0]?.newRefName, "t3code-abcd1234"); | 1195 | assert.equal(harness.createWorktree.mock.calls[0]?.[0]?.newRefName, "t3-abcd1234"); |
| 1196 | assert.equal(harness.renameBranch.mock.calls[0]?.[0]?.oldBranch, "t3code-abcd1234"); | 1196 | assert.equal(harness.renameBranch.mock.calls[0]?.[0]?.oldBranch, "t3-abcd1234"); |
| 1197 | }).pipe(Effect.provide(harness.layer)); | 1197 | }).pipe(Effect.provide(harness.layer)); |
| 1198 | }), | 1198 | }), |
| 1199 | ); | 1199 | ); |
| ... | @@ -1235,11 +1235,11 @@ it.effect("keeps the temporary branch when branch generation fails", () => | ... | @@ -1235,11 +1235,11 @@ it.effect("keeps the temporary branch when branch generation fails", () => |
| 1235 | command: "command:launch:branch-fallback", | 1235 | command: "command:launch:branch-fallback", |
| 1236 | thread: "thread:launch:branch-fallback", | 1236 | thread: "thread:launch:branch-fallback", |
| 1237 | message: "Build the feature", | 1237 | message: "Build the feature", |
| 1238 | workspace: { type: "worktree", baseRef: "main", branch: "t3code/abcd1234" }, | 1238 | workspace: { type: "worktree", baseRef: "main", branch: "t3/abcd1234" }, |
| 1239 | }), | 1239 | }), |
| 1240 | ); | 1240 | ); |
| 1241 | yield* waitUntil(() => Effect.sync(() => harness.generateBranchName.mock.calls.length === 1)); | 1241 | yield* waitUntil(() => Effect.sync(() => harness.generateBranchName.mock.calls.length === 1)); |
| 1242 | assert.equal(harness.createWorktree.mock.calls[0]?.[0]?.newRefName, "t3code/abcd1234"); | 1242 | assert.equal(harness.createWorktree.mock.calls[0]?.[0]?.newRefName, "t3/abcd1234"); |
| 1243 | yield* waitUntil(() => | 1243 | yield* waitUntil(() => |
| 1244 | threads | 1244 | threads |
| 1245 | .getThreadProjection(launched.threadId) | 1245 | .getThreadProjection(launched.threadId) |
| ... | @@ -1248,7 +1248,7 @@ it.effect("keeps the temporary branch when branch generation fails", () => | ... | @@ -1248,7 +1248,7 @@ it.effect("keeps the temporary branch when branch generation fails", () => |
| 1248 | assert.equal(harness.renameBranch.mock.calls.length, 0); | 1248 | assert.equal(harness.renameBranch.mock.calls.length, 0); |
| 1249 | assert.equal( | 1249 | assert.equal( |
| 1250 | (yield* threads.getThreadProjection(launched.threadId)).thread.branch, | 1250 | (yield* threads.getThreadProjection(launched.threadId)).thread.branch, |
| 1251 | "t3code/abcd1234", | 1251 | "t3/abcd1234", |
| 1252 | ); | 1252 | ); |
| 1253 | }).pipe(Effect.provide(harness.layer)); | 1253 | }).pipe(Effect.provide(harness.layer)); |
| 1254 | }), | 1254 | }), |
| ... | @@ -1267,8 +1267,8 @@ it.effect("renames a temporary branch on an existing worktree to a generated nam | ... | @@ -1267,8 +1267,8 @@ it.effect("renames a temporary branch on an existing worktree to a generated nam |
| 1267 | message: "Build the feature", | 1267 | message: "Build the feature", |
| 1268 | workspace: { | 1268 | workspace: { |
| 1269 | type: "existing_worktree", | 1269 | type: "existing_worktree", |
| 1270 | worktreePath: "/repo-worktrees/t3code-abcd1234", | 1270 | worktreePath: "/repo-worktrees/t3-abcd1234", |
| 1271 | branch: "t3code/abcd1234", | 1271 | branch: "t3/abcd1234", |
| 1272 | }, | 1272 | }, |
| 1273 | }), | 1273 | }), |
| 1274 | ); | 1274 | ); |
| ... | @@ -1278,8 +1278,8 @@ it.effect("renames a temporary branch on an existing worktree to a generated nam | ... | @@ -1278,8 +1278,8 @@ it.effect("renames a temporary branch on an existing worktree to a generated nam |
| 1278 | .pipe(Effect.map((projection) => projection.thread.branch === "generated-branch")), | 1278 | .pipe(Effect.map((projection) => projection.thread.branch === "generated-branch")), |
| 1279 | ); | 1279 | ); |
| 1280 | assert.deepEqual(harness.renameBranch.mock.calls[0]?.[0], { | 1280 | assert.deepEqual(harness.renameBranch.mock.calls[0]?.[0], { |
| 1281 | cwd: "/repo-worktrees/t3code-abcd1234", | 1281 | cwd: "/repo-worktrees/t3-abcd1234", |
| 1282 | oldBranch: "t3code/abcd1234", | 1282 | oldBranch: "t3/abcd1234", |
| 1283 | newBranch: "generated-branch", | 1283 | newBranch: "generated-branch", |
| 1284 | }); | 1284 | }); |
| 1285 | }).pipe(Effect.provide(harness.layer)); | 1285 | }).pipe(Effect.provide(harness.layer)); |
apps/server/src/orchestration-v2/ThreadLaunchService.ts+1-1| ... | @@ -304,7 +304,7 @@ const make = Effect.gen(function* () { | ... | @@ -304,7 +304,7 @@ const make = Effect.gen(function* () { |
| 304 | }); | 304 | }); |
| 305 | 305 | ||
| 306 | // The server owns worktree naming: without an explicit branch, provision | 306 | // The server owns worktree naming: without an explicit branch, provision |
| 307 | // under a temporary `t3code/<hash>` name so the worktree never waits on | 307 | // under a temporary `t3/<hash>` name so the worktree never waits on |
| 308 | // name generation, then rename in the background below. | 308 | // name generation, then rename in the background below. |
| 309 | const requestedBranch = input.workspaceStrategy.branch; | 309 | const requestedBranch = input.workspaceStrategy.branch; |
| 310 | let branch: string | null; | 310 | let branch: string | null; |
apps/web/src/components/settings/BranchNamingSettings.tsx+1-1| ... | @@ -76,7 +76,7 @@ export function BranchNamingSettings() { | ... | @@ -76,7 +76,7 @@ export function BranchNamingSettings() { |
| 76 | serverScoped | 76 | serverScoped |
| 77 | settingKeys={["branchNamePrefix"]} | 77 | settingKeys={["branchNamePrefix"]} |
| 78 | title="Branch prefix" | 78 | title="Branch prefix" |
| 79 | description="For example, t3code or t3code/ produces t3code/add-search. Leave empty for no prefix." | 79 | description="For example, t3 or t3/ produces t3/add-search. Leave empty for no prefix." |
| 80 | resetAction={ | 80 | resetAction={ |
| 81 | prefixMixed || | 81 | prefixMixed || |
| 82 | settings.branchNamePrefix !== DEFAULT_SERVER_SETTINGS.branchNamePrefix ? ( | 82 | settings.branchNamePrefix !== DEFAULT_SERVER_SETTINGS.branchNamePrefix ? ( |
docs/user/project-settings.md+1-1| ... | @@ -42,7 +42,7 @@ and other phone-only settings ignore the filter. | ... | @@ -42,7 +42,7 @@ and other phone-only settings ignore the filter. |
| 42 | 42 | ||
| 43 | In **Settings → Source Control → Worktree branch naming**, choose a static prefix, | 43 | In **Settings → Source Control → Worktree branch naming**, choose a static prefix, |
| 44 | a model-selected semantic prefix such as `feat/` or `fix/`, or custom instructions | 44 | a model-selected semantic prefix such as `feat/` or `fix/`, or custom instructions |
| 45 | for the complete name. The static prefix defaults to `t3code/`; a trailing slash is | 45 | for the complete name. The static prefix defaults to `t3/`; a trailing slash is |
| 46 | optional, and an empty prefix adds nothing. Invalid characters in a static prefix | 46 | optional, and an empty prefix adds nothing. Invalid characters in a static prefix |
| 47 | are replaced with hyphens. Custom instructions are appended to | 47 | are replaced with hyphens. Custom instructions are appended to |
| 48 | the naming prompt and can specify issue IDs, namespaces, and casing. | 48 | the naming prompt and can specify issue IDs, namespaces, and casing. |
packages/contracts/src/settings.test.ts+2-2| ... | @@ -1085,10 +1085,10 @@ it("validates remote device hosts and rejects ambiguous host ids", () => { | ... | @@ -1085,10 +1085,10 @@ it("validates remote device hosts and rejects ambiguous host ids", () => { |
| 1085 | }); | 1085 | }); |
| 1086 | 1086 | ||
| 1087 | describe("branch naming settings", () => { | 1087 | describe("branch naming settings", () => { |
| 1088 | it("defaults existing settings to the t3code static prefix", () => { | 1088 | it("defaults existing settings to the t3 static prefix", () => { |
| 1089 | expect(decodeServerSettings({})).toMatchObject({ | 1089 | expect(decodeServerSettings({})).toMatchObject({ |
| 1090 | branchNamingMode: "static", | 1090 | branchNamingMode: "static", |
| 1091 | branchNamePrefix: "t3code", | 1091 | branchNamePrefix: "t3", |
| 1092 | branchNameInstructions: "", | 1092 | branchNameInstructions: "", |
| 1093 | }); | 1093 | }); |
| 1094 | }); | 1094 | }); |
packages/contracts/src/settings.ts+1-1| ... | @@ -1368,7 +1368,7 @@ export const ServerSettings = Schema.Struct({ | ... | @@ -1368,7 +1368,7 @@ export const ServerSettings = Schema.Struct({ |
| 1368 | branchNamingMode: BranchNamingMode.pipe( | 1368 | branchNamingMode: BranchNamingMode.pipe( |
| 1369 | Schema.withDecodingDefault(Effect.succeed("static" as const)), | 1369 | Schema.withDecodingDefault(Effect.succeed("static" as const)), |
| 1370 | ), | 1370 | ), |
| 1371 | branchNamePrefix: TrimmedString.pipe(Schema.withDecodingDefault(Effect.succeed("t3code"))), | 1371 | branchNamePrefix: TrimmedString.pipe(Schema.withDecodingDefault(Effect.succeed("t3"))), |
| 1372 | branchNameInstructions: TrimmedString.pipe(Schema.withDecodingDefault(Effect.succeed(""))), | 1372 | branchNameInstructions: TrimmedString.pipe(Schema.withDecodingDefault(Effect.succeed(""))), |
| 1373 | removeAgentCreditsOnMerge: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))), | 1373 | removeAgentCreditsOnMerge: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))), |
| 1374 | sourceControlWritingStyle: SourceControlWritingStyleSettings.pipe( | 1374 | sourceControlWritingStyle: SourceControlWritingStyleSettings.pipe( |
packages/shared/src/git.test.ts+17-14| ... | @@ -190,34 +190,37 @@ describe("isTemporaryWorktreeBranch", () => { | ... | @@ -190,34 +190,37 @@ describe("isTemporaryWorktreeBranch", () => { |
| 190 | ); | 190 | ); |
| 191 | }); | 191 | }); |
| 192 | 192 | ||
| 193 | it("matches legacy UUID-shaped temporary worktree refs from older mobile builds", () => { | 193 | it("matches legacy t3code temporary worktree refs", () => { |
| 194 | expect(isTemporaryWorktreeBranch("t3code/deadbeef")).toBe(true); | ||
| 195 | expect(isTemporaryWorktreeBranch("t3code-deadbeef")).toBe(true); | ||
| 196 | expect(isTemporaryWorktreeBranch("t3code/f4ae4e0e-f971-4d48-b4f2-9cf0aa54ab12")).toBe(true); | ||
| 194 | expect( | 197 | expect( |
| 195 | isTemporaryWorktreeBranch(`${WORKTREE_BRANCH_PREFIX}/f4ae4e0e-f971-4d48-b4f2-9cf0aa54ab12`), | 198 | isTemporaryWorktreeBranch(`${WORKTREE_BRANCH_PREFIX}/f4ae4e0e-f971-4d48-b4f2-9cf0aa54ab12`), |
| 196 | ).toBe(true); | 199 | ).toBe(false); |
| 197 | }); | 200 | }); |
| 198 | 201 | ||
| 199 | it("rejects UUID-shaped refs that are not RFC 4122 v4", () => { | 202 | it("rejects UUID-shaped refs that are not RFC 4122 v4", () => { |
| 200 | // version nibble is not 4 | 203 | // version nibble is not 4 |
| 201 | expect( | 204 | expect(isTemporaryWorktreeBranch("t3code/f4ae4e0e-f971-1d48-b4f2-9cf0aa54ab12")).toBe(false); |
| 202 | isTemporaryWorktreeBranch(`${WORKTREE_BRANCH_PREFIX}/f4ae4e0e-f971-1d48-b4f2-9cf0aa54ab12`), | ||
| 203 | ).toBe(false); | ||
| 204 | // variant nibble is not [89ab] | 205 | // variant nibble is not [89ab] |
| 205 | expect( | 206 | expect(isTemporaryWorktreeBranch("t3code/f4ae4e0e-f971-4d48-c4f2-9cf0aa54ab12")).toBe(false); |
| 206 | isTemporaryWorktreeBranch(`${WORKTREE_BRANCH_PREFIX}/f4ae4e0e-f971-4d48-c4f2-9cf0aa54ab12`), | ||
| 207 | ).toBe(false); | ||
| 208 | }); | 207 | }); |
| 209 | 208 | ||
| 210 | it("matches the flat fallback used when a plain t3code branch exists", () => { | 209 | it("matches the flat fallback used when a plain t3 branch exists", () => { |
| 211 | const flat = flattenTemporaryWorktreeBranchName(`${WORKTREE_BRANCH_PREFIX}/deadbeef`); | 210 | const flat = flattenTemporaryWorktreeBranchName(`${WORKTREE_BRANCH_PREFIX}/deadbeef`); |
| 212 | expect(flat).toBe(`${WORKTREE_BRANCH_PREFIX}-deadbeef`); | 211 | expect(flat).toBe(`${WORKTREE_BRANCH_PREFIX}-deadbeef`); |
| 213 | expect(isTemporaryWorktreeBranch(flat)).toBe(true); | 212 | expect(isTemporaryWorktreeBranch(flat)).toBe(true); |
| 214 | expect(isTemporaryWorktreeBranch(`${WORKTREE_BRANCH_PREFIX}-deadbeef-extra`)).toBe(false); | 213 | expect(isTemporaryWorktreeBranch(`${WORKTREE_BRANCH_PREFIX}-deadbeef-extra`)).toBe(false); |
| 215 | expect(isTemporaryWorktreeBranch(`${WORKTREE_BRANCH_PREFIX}-feature`)).toBe(false); | 214 | expect(isTemporaryWorktreeBranch(`${WORKTREE_BRANCH_PREFIX}-feature`)).toBe(false); |
| 216 | expect( | 215 | expect(flattenTemporaryWorktreeBranchName("t3code/f4ae4e0e-f971-4d48-b4f2-9cf0aa54ab12")).toBe( |
| 217 | flattenTemporaryWorktreeBranchName( | 216 | `${WORKTREE_BRANCH_PREFIX}-f4ae4e0e`, |
| 218 | `${WORKTREE_BRANCH_PREFIX}/f4ae4e0e-f971-4d48-b4f2-9cf0aa54ab12`, | 217 | ); |
| 219 | ), | 218 | expect(flattenTemporaryWorktreeBranchName("t3code-deadbeef")).toBe( |
| 220 | ).toBe(`${WORKTREE_BRANCH_PREFIX}-f4ae4e0e`); | 219 | `${WORKTREE_BRANCH_PREFIX}-deadbeef`, |
| 220 | ); | ||
| 221 | expect(flattenTemporaryWorktreeBranchName("t3code/deadbeef")).toBe( | ||
| 222 | `${WORKTREE_BRANCH_PREFIX}-deadbeef`, | ||
| 223 | ); | ||
| 221 | }); | 224 | }); |
| 222 | 225 | ||
| 223 | it("rejects non-temporary refName names", () => { | 226 | it("rejects non-temporary refName names", () => { |
packages/shared/src/git.ts+17-14| ... | @@ -11,14 +11,18 @@ import * as Arr from "effect/Array"; | ... | @@ -11,14 +11,18 @@ import * as Arr from "effect/Array"; |
| 11 | import * as Result from "effect/Result"; | 11 | import * as Result from "effect/Result"; |
| 12 | import { detectSourceControlProviderFromRemoteUrl } from "./sourceControl.ts"; | 12 | import { detectSourceControlProviderFromRemoteUrl } from "./sourceControl.ts"; |
| 13 | 13 | ||
| 14 | export const WORKTREE_BRANCH_PREFIX = "t3code"; | 14 | export const WORKTREE_BRANCH_PREFIX = "t3"; |
| 15 | // Canonical form is `t3code/<8 hex>`. Older mobile builds generated `t3code/<uuid>` | 15 | // Canonical form is `t3/<8 hex>`. `t3-<8 hex>` is the fallback when a plain `t3` |
| 16 | // via Crypto.randomUUID() (always RFC 4122 v4), so the matcher also accepts exactly | 16 | // branch blocks the namespace. The matcher also accepts every legacy shape, so |
| 17 | // that shape — version nibble `4`, variant nibble `[89ab]` — to keep those threads | 17 | // existing threads stay eligible for branch regeneration: `t3code/<8 hex>` and |
| 18 | // eligible for branch regeneration without loosening beyond what was ever generated. | 18 | // `t3code-<8 hex>` from before the prefix was shortened, and `t3code/<uuid>` from |
| 19 | // `t3code-<8 hex>` is the fallback when a plain `t3code` branch blocks the namespace. | 19 | // older mobile builds that used Crypto.randomUUID() (always RFC 4122 v4, so version |
| 20 | // nibble `4` and variant nibble `[89ab]`). Nothing looser than what was generated. | ||
| 21 | const TEMP_WORKTREE_HEX_TOKEN = "[0-9a-f]{8}"; | ||
| 22 | const TEMP_WORKTREE_UUID_V4_TOKEN = | ||
| 23 | "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}"; | ||
| 20 | const TEMP_WORKTREE_BRANCH_PATTERN = new RegExp( | 24 | const TEMP_WORKTREE_BRANCH_PATTERN = new RegExp( |
| 21 | `^${WORKTREE_BRANCH_PREFIX}(?:-[0-9a-f]{8}$|\\/(?:[0-9a-f]{8}|[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})$)`, | 25 | `^(?:${WORKTREE_BRANCH_PREFIX}[-/]${TEMP_WORKTREE_HEX_TOKEN}|t3code(?:[-/]${TEMP_WORKTREE_HEX_TOKEN}|\\/${TEMP_WORKTREE_UUID_V4_TOKEN}))$`, |
| 22 | ); | 26 | ); |
| 23 | 27 | ||
| 24 | /** | 28 | /** |
| ... | @@ -125,15 +129,14 @@ export function buildTemporaryWorktreeBranchName( | ... | @@ -125,15 +129,14 @@ export function buildTemporaryWorktreeBranchName( |
| 125 | } | 129 | } |
| 126 | 130 | ||
| 127 | /** | 131 | /** |
| 128 | * Git stores refs as paths, so a plain `t3code` branch makes every `t3code/<hex>` | 132 | * Git stores refs as paths, so a plain `t3` branch makes every `t3/<hex>` |
| 129 | * ref impossible. This moves a temporary name to the flat `t3code-<hex>` sibling. | 133 | * ref impossible. This moves a temporary name to the flat `t3-<hex>` sibling. |
| 130 | */ | 134 | */ |
| 131 | export function flattenTemporaryWorktreeBranchName(refName: string): string { | 135 | export function flattenTemporaryWorktreeBranchName(refName: string): string { |
| 132 | // Keep only the canonical 8-hex token so legacy UUID names stay recognizable. | 136 | // Keep only the canonical 8-hex token so legacy `t3code/` and UUID names map cleanly. |
| 133 | const token = refName | 137 | const normalized = refName.trim().toLowerCase(); |
| 134 | .trim() | 138 | const tokenStart = normalized.search(/[-/]/) + 1; |
| 135 | .toLowerCase() | 139 | const token = normalized.slice(tokenStart, tokenStart + 8); |
| 136 | .slice(WORKTREE_BRANCH_PREFIX.length + 1, WORKTREE_BRANCH_PREFIX.length + 9); | ||
| 137 | return `${WORKTREE_BRANCH_PREFIX}-${token}`; | 140 | return `${WORKTREE_BRANCH_PREFIX}-${token}`; |
| 138 | } | 141 | } |
| 139 | 142 |