| author | |
| committer | |
| log | c0995d2eaf8ec787b3318ed1169ae266ed1529f8 |
| tree | 4d4ec0e589d0c5f34da675bebc7bec5be2fc18fd |
| parent | d0b4acbd13b2b602710e4a7d60c42f4799a409be |
| signature | Signed by PGP key B5690EEEBB952194 |
3 files changed, 81 insertions(+), 7 deletions(-)
apps/web/src/environmentGrouping.test.ts+68| ... | @@ -323,6 +323,74 @@ describe("environment grouping", () => { | ... | @@ -323,6 +323,74 @@ describe("environment grouping", () => { |
| 323 | expect(entries[1]?.group.displayName).toBe("separate"); | 323 | expect(entries[1]?.group.displayName).toBe("separate"); |
| 324 | }); | 324 | }); |
| 325 | 325 | ||
| 326 | it("keeps the current environment when available and falls back otherwise", () => { | ||
| 327 | const currentPrimary = makeProject({ repositoryIdentity }); | ||
| 328 | const currentRemote = makeProject({ | ||
| 329 | id: ProjectId.make("current-remote"), | ||
| 330 | environmentId: remoteEnvironmentId, | ||
| 331 | repositoryIdentity, | ||
| 332 | }); | ||
| 333 | const destinationRepositoryIdentity = { | ||
| 334 | canonicalKey: "github.com/example/destination", | ||
| 335 | locator: { | ||
| 336 | source: "git-remote" as const, | ||
| 337 | remoteName: "origin", | ||
| 338 | remoteUrl: "https://github.com/example/destination.git", | ||
| 339 | }, | ||
| 340 | }; | ||
| 341 | const destinationPrimary = makeProject({ | ||
| 342 | id: ProjectId.make("destination-primary"), | ||
| 343 | title: "destination", | ||
| 344 | workspaceRoot: "/tmp/destination", | ||
| 345 | repositoryIdentity: destinationRepositoryIdentity, | ||
| 346 | }); | ||
| 347 | const destinationRemote = makeProject({ | ||
| 348 | id: ProjectId.make("destination-remote"), | ||
| 349 | environmentId: remoteEnvironmentId, | ||
| 350 | title: "destination", | ||
| 351 | workspaceRoot: "/remote/destination", | ||
| 352 | repositoryIdentity: destinationRepositoryIdentity, | ||
| 353 | }); | ||
| 354 | const fallbackPrimary = makeProject({ | ||
| 355 | id: ProjectId.make("fallback-primary"), | ||
| 356 | title: "fallback", | ||
| 357 | workspaceRoot: "/tmp/fallback", | ||
| 358 | }); | ||
| 359 | const groups = buildSidebarProjectSnapshots({ | ||
| 360 | projects: [ | ||
| 361 | currentPrimary, | ||
| 362 | currentRemote, | ||
| 363 | destinationPrimary, | ||
| 364 | destinationRemote, | ||
| 365 | fallbackPrimary, | ||
| 366 | ], | ||
| 367 | settings: defaultGroupingSettings, | ||
| 368 | primaryEnvironmentId, | ||
| 369 | resolveEnvironmentLabel: () => null, | ||
| 370 | }); | ||
| 371 | |||
| 372 | const entries = buildSidebarProjectPickerEntries({ | ||
| 373 | groups, | ||
| 374 | preferredProjectRef: { | ||
| 375 | environmentId: remoteEnvironmentId, | ||
| 376 | projectId: currentRemote.id, | ||
| 377 | }, | ||
| 378 | }); | ||
| 379 | const destination = entries.find( | ||
| 380 | (entry) => entry.group.projectKey === destinationRepositoryIdentity.canonicalKey, | ||
| 381 | ); | ||
| 382 | const fallback = entries.find((entry) => entry.group.displayName === "fallback"); | ||
| 383 | |||
| 384 | expect(destination?.targetProject).toMatchObject({ | ||
| 385 | environmentId: remoteEnvironmentId, | ||
| 386 | id: destinationRemote.id, | ||
| 387 | }); | ||
| 388 | expect(fallback?.targetProject).toMatchObject({ | ||
| 389 | environmentId: primaryEnvironmentId, | ||
| 390 | id: fallbackPrimary.id, | ||
| 391 | }); | ||
| 392 | }); | ||
| 393 | |||
| 326 | it("keeps manual project order when building grouped sidebar entries", () => { | 394 | it("keeps manual project order when building grouped sidebar entries", () => { |
| 327 | const primary = makeProject({ repositoryIdentity }); | 395 | const primary = makeProject({ repositoryIdentity }); |
| 328 | const remote = makeProject({ | 396 | const remote = makeProject({ |
apps/web/src/sidebarProjectGrouping.ts+8-7| ... | @@ -118,22 +118,23 @@ export function buildSidebarProjectPickerEntries(input: { | ... | @@ -118,22 +118,23 @@ export function buildSidebarProjectPickerEntries(input: { |
| 118 | groups: ReadonlyArray<SidebarProjectSnapshot>; | 118 | groups: ReadonlyArray<SidebarProjectSnapshot>; |
| 119 | preferredProjectRef: ScopedProjectRef | null; | 119 | preferredProjectRef: ScopedProjectRef | null; |
| 120 | }) { | 120 | }) { |
| 121 | const preferredProjectRef = input.preferredProjectRef; | ||
| 121 | const entries = input.groups.flatMap((group): SidebarProjectPickerEntry[] => { | 122 | const entries = input.groups.flatMap((group): SidebarProjectPickerEntry[] => { |
| 122 | const isPreferred = input.preferredProjectRef | 123 | const isPreferred = preferredProjectRef |
| 123 | ? group.memberProjectRefs.some( | 124 | ? group.memberProjectRefs.some( |
| 124 | (projectRef) => | 125 | (projectRef) => |
| 125 | projectRef.environmentId === input.preferredProjectRef?.environmentId && | 126 | projectRef.environmentId === preferredProjectRef.environmentId && |
| 126 | projectRef.projectId === input.preferredProjectRef.projectId, | 127 | projectRef.projectId === preferredProjectRef.projectId, |
| 127 | ) | 128 | ) |
| 128 | : false; | 129 | : false; |
| 129 | const preferredProject = isPreferred | 130 | const preferredProject = preferredProjectRef |
| 130 | ? (group.memberProjects.find( | 131 | ? (group.memberProjects.find( |
| 131 | (project) => | 132 | (project) => |
| 132 | project.environmentId === input.preferredProjectRef?.environmentId && | 133 | project.environmentId === preferredProjectRef.environmentId && |
| 133 | project.id === input.preferredProjectRef?.projectId, | 134 | project.id === preferredProjectRef.projectId, |
| 134 | ) ?? | 135 | ) ?? |
| 135 | group.memberProjects.find( | 136 | group.memberProjects.find( |
| 136 | (project) => project.environmentId === input.preferredProjectRef?.environmentId, | 137 | (project) => project.environmentId === preferredProjectRef.environmentId, |
| 137 | )) | 138 | )) |
| 138 | : null; | 139 | : null; |
| 139 | const targetProject = | 140 | const targetProject = |
docs/user/composer.md+5| ... | @@ -52,6 +52,11 @@ On mobile, the model picker shows each OpenCode model's upstream provider, such | ... | @@ -52,6 +52,11 @@ On mobile, the model picker shows each OpenCode model's upstream provider, such |
| 52 | GitHub Copilot, or OpenCode Zen, beneath its name. Search by that provider name to narrow the list | 52 | GitHub Copilot, or OpenCode Zen, beneath its name. Search by that provider name to narrow the list |
| 53 | when starting a thread or changing an existing thread's model. | 53 | when starting a thread or changing an existing thread's model. |
| 54 | 54 | ||
| 55 | ## Changing projects | ||
| 56 | |||
| 57 | On web and desktop, changing the project from a new thread keeps the current environment when that | ||
| 58 | project exists there. If it does not, T3 Code selects another environment that has the project. | ||
| 59 | |||
| 55 | ## Notices above the composer | 60 | ## Notices above the composer |
| 56 | 61 | ||
| 57 | On web and desktop, loading and syncing statuses fill the available banner width beside the | 62 | On web and desktop, loading and syncing statuses fill the available banner width beside the |