| author | |
| committer | |
| log | 6eed953a060213630af0d7e3cd9a664bd85f7ced |
| tree | 90231578e8d75f9846f6bb59a4ed5e3fed2abff8 |
| parent | 6651aaab38c6814ed7bba3e805a1ec7a01870b55 |
| signature | Signed by PGP key B5690EEEBB952194 |
3 files changed, 61 insertions(+), 34 deletions(-)
apps/mobile/src/features/home/HomeScreen.tsx+5-33| ... | ... | @@ -67,7 +67,7 @@ import { useThreadListV2ShelfPreferences } from "../threads/use-thread-list-v2-s |
| 67 | 67 | import type { HomeListFilterMenuEnvironment } from "./home-list-filter-menu"; |
| 68 | 68 | import { |
| 69 | 69 | buildHomeProjectScopes, |
| 70 | sortHomeProjectScopes, | |
| 70 | findHomeProjectScope, | |
| 71 | 71 | type HomeProjectSortOrder, |
| 72 | 72 | } from "./homeThreadList"; |
| 73 | 73 | import { createSwipeRowActivation } from "./swipe-row-activation"; |
| ... | ... | @@ -386,42 +386,14 @@ export function HomeScreen(props: HomeScreenProps) { |
| 386 | 386 | }, [props.projects]); |
| 387 | 387 | |
| 388 | 388 | const v2ProjectScopeKey = props.selectedProjectKey; |
| 389 | const v2ScopeProjects = useMemo( | |
| 390 | () => | |
| 391 | sortHomeProjectScopes({ | |
| 392 | scopes: projectScopes, | |
| 393 | threads: props.threads, | |
| 394 | pendingTasks: props.pendingTasks, | |
| 395 | projectSortOrder: props.projectSortOrder, | |
| 396 | }), | |
| 397 | [ | |
| 398 | props.pendingTasks, | |
| 399 | props.projects, | |
| 400 | props.projectSortOrder, | |
| 401 | props.selectedEnvironmentId, | |
| 402 | props.threads, | |
| 403 | projectScopes, | |
| 404 | ], | |
| 405 | ); | |
| 406 | 389 | const v2ScopedProjectGroup = useMemo( |
| 407 | () => | |
| 408 | v2ProjectScopeKey === null | |
| 409 | ? null | |
| 410 | : (v2ScopeProjects.find( | |
| 411 | (scope) => | |
| 412 | scope.key === v2ProjectScopeKey || | |
| 413 | scope.projectRefs.some( | |
| 414 | (projectRef) => | |
| 415 | scopedProjectKey(projectRef.environmentId, projectRef.projectId) === | |
| 416 | v2ProjectScopeKey, | |
| 417 | ), | |
| 418 | ) ?? null), | |
| 419 | [v2ProjectScopeKey, v2ScopeProjects], | |
| 390 | () => findHomeProjectScope(projectScopes, v2ProjectScopeKey), | |
| 391 | [v2ProjectScopeKey, projectScopes], | |
| 420 | 392 | ); |
| 421 | 393 | const v2ProjectTitleByProjectKey = useMemo( |
| 422 | 394 | () => |
| 423 | 395 | new Map( |
| 424 | v2ScopeProjects.flatMap((scope) => | |
| 396 | projectScopes.flatMap((scope) => | |
| 425 | 397 | scope.projectRefs.map( |
| 426 | 398 | (projectRef) => |
| 427 | 399 | [ |
| ... | ... | @@ -431,7 +403,7 @@ export function HomeScreen(props: HomeScreenProps) { |
| 431 | 403 | ), |
| 432 | 404 | ), |
| 433 | 405 | ), |
| 434 | [v2ScopeProjects], | |
| 406 | [projectScopes], | |
| 435 | 407 | ); |
| 436 | 408 | const v2ScopedProjectKeys = useMemo( |
| 437 | 409 | () => |
apps/mobile/src/features/home/homeThreadList.test.ts+36-1| ... | ... | @@ -5,7 +5,11 @@ import type { |
| 5 | 5 | import { EnvironmentId, ProjectId, ProviderInstanceId, ThreadId } from "@t3tools/contracts"; |
| 6 | 6 | import { describe, expect, it } from "vite-plus/test"; |
| 7 | 7 | |
| 8 | import { buildHomeProjectScopes, sortHomeProjectScopes } from "./homeThreadList"; | |
| 8 | import { | |
| 9 | buildHomeProjectScopes, | |
| 10 | findHomeProjectScope, | |
| 11 | sortHomeProjectScopes, | |
| 12 | } from "./homeThreadList"; | |
| 9 | 13 | import { makeThreadShellFixture } from "../../test-fixtures"; |
| 10 | 14 | |
| 11 | 15 | function makeProject( |
| ... | ... | @@ -282,4 +286,35 @@ describe("home project scopes", () => { |
| 282 | 286 | }), |
| 283 | 287 | ).toHaveLength(2); |
| 284 | 288 | }); |
| 289 | ||
| 290 | it("prefers an exact scope key over a project ID that collides with it", () => { | |
| 291 | const environmentId = EnvironmentId.make("environment-local"); | |
| 292 | // project.create accepts caller-supplied IDs, so one project's scoped ref | |
| 293 | // key can equal another project's normalized workspace-root scope key. | |
| 294 | const collidingProject = makeProject({ | |
| 295 | environmentId, | |
| 296 | id: ProjectId.make("/workspaces/target"), | |
| 297 | title: "Colliding", | |
| 298 | workspaceRoot: "/workspaces/colliding", | |
| 299 | }); | |
| 300 | const targetProject = makeProject({ | |
| 301 | environmentId, | |
| 302 | id: ProjectId.make("project-target"), | |
| 303 | title: "Target", | |
| 304 | workspaceRoot: "/workspaces/target", | |
| 305 | }); | |
| 306 | const scopes = buildHomeProjectScopes({ | |
| 307 | projects: [collidingProject, targetProject], | |
| 308 | environmentId: null, | |
| 309 | projectGroupingMode: "separate", | |
| 310 | }); | |
| 311 | const targetScope = scopes.find((scope) => scope.title === "Target"); | |
| 312 | expect(scopes.map((scope) => scope.title)).toEqual(["Colliding", "Target"]); | |
| 313 | expect(targetScope?.key).toBe("environment-local:/workspaces/target"); | |
| 314 | ||
| 315 | expect(findHomeProjectScope(scopes, "environment-local:/workspaces/target")).toBe(targetScope); | |
| 316 | expect(findHomeProjectScope(scopes, "environment-local:project-target")).toBe(targetScope); | |
| 317 | expect(findHomeProjectScope(scopes, "environment-local:missing")).toBeNull(); | |
| 318 | expect(findHomeProjectScope(scopes, null)).toBeNull(); | |
| 319 | }); | |
| 285 | 320 | }); |
apps/mobile/src/features/home/homeThreadList.ts+20| ... | ... | @@ -65,6 +65,26 @@ export function buildHomeProjectScopes(input: { |
| 65 | 65 | }); |
| 66 | 66 | } |
| 67 | 67 | |
| 68 | // Prefers an exact scope key so a caller-supplied project ID that equals another | |
| 69 | // project's normalized workspace root cannot select the wrong group. | |
| 70 | export function findHomeProjectScope( | |
| 71 | scopes: ReadonlyArray<HomeProjectScope>, | |
| 72 | key: string | null, | |
| 73 | ): HomeProjectScope | null { | |
| 74 | if (key === null) { | |
| 75 | return null; | |
| 76 | } | |
| 77 | return ( | |
| 78 | scopes.find((scope) => scope.key === key) ?? | |
| 79 | scopes.find((scope) => | |
| 80 | scope.projectRefs.some( | |
| 81 | (projectRef) => scopedProjectKey(projectRef.environmentId, projectRef.projectId) === key, | |
| 82 | ), | |
| 83 | ) ?? | |
| 84 | null | |
| 85 | ); | |
| 86 | } | |
| 87 | ||
| 68 | 88 | export function sortHomeProjectScopes(input: { |
| 69 | 89 | readonly scopes: ReadonlyArray<HomeProjectScope>; |
| 70 | 90 | readonly threads: ReadonlyArray<EnvironmentThreadShell>; |