authorgravatar for 97427849+flamboh@users.noreply.github.comoliver <97427849+flamboh@users.noreply.github.com> 2026-10-14 10:49:32-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-10-14 17:49:32+00:00
logcb395ee13b5b5ddd4352e7c3d4216181501cc2e5
treedf70cd8a05e11a7cbb18a74a9fc1b9d51df03c92
parente093789dd2340d8a41ee7ac12341fb011f9ff86c
signature Signed by PGP key B5690EEEBB952194

fix(web,mobile): pull request filters no longer offer the Scratch folder (#19179)


4 files changed, 67 insertions(+), 5 deletions(-)

apps/mobile/src/features/pull-requests/PullRequestsScreen.tsx+10-3
......@@ -34,6 +34,7 @@ import {
3434 scopedPullRequestListKey as pullRequestKey,
3535 type EnvironmentPullRequestListEntry,
3636 PR_LIST_PAGE_SIZE,
37 withoutScratchProjectScope,
3738} from "./pullRequestList";
3839import { useDebouncedValue } from "../../state/queries";
3940import { useWorkspaceTabs } from "../workspace/WorkspaceTabsContext";
......@@ -102,7 +103,12 @@ export function PullRequestsScreen(
102103 },
103104 [navigation, selectAndroidWorkspace],
104105 );
105 const controls = listControls.controlsFor(environmentId);
106 const allProjects = useProjects();
107 const savedControls = listControls.controlsFor(environmentId);
108 const controls = useMemo(
109 () => withoutScratchProjectScope(savedControls, allProjects),
110 [allProjects, savedControls],
111 );
106112 const targets = useMemo(
107113 () =>
108114 environments
......@@ -175,8 +181,9 @@ export function PullRequestsScreen(
175181 ].sort(),
176182 }));
177183 }
178 const projects = useProjects().filter(
179 (project) => environmentId === null || project.environmentId === environmentId,
184 const projects = allProjects.filter(
185 (project) =>
186 !project.isScratch && (environmentId === null || project.environmentId === environmentId),
180187 );
181188 const renderPullRequest = useCallback(
182189 ({ item }: { readonly item: EnvironmentPullRequestListEntry }) => (
apps/mobile/src/features/pull-requests/pullRequestList.test.ts+37
......@@ -18,6 +18,7 @@ import {
1818 pullRequestLabelColor,
1919 scopedPullRequestListKey,
2020 pullRequestListRowsAreEqual,
21 withoutScratchProjectScope,
2122} from "./pullRequestList";
2223
2324function entry(number: number, patch: Partial<PullRequestListEntry> = {}): PullRequestListEntry {
......@@ -373,3 +374,39 @@ describe("mobile PR scan and filters", () => {
373374 expect(pullRequestLabelColor("not-a-color")).toBeNull();
374375 });
375376});
377
378describe("withoutScratchProjectScope", () => {
379 const projects = [
380 { id: "repo", environmentId: "env-a" },
381 { id: "scratch", environmentId: "env-a", isScratch: true as const },
382 ];
383
384 it("reads a scope saved on the Scratch folder as all projects", () => {
385 const controls = withoutScratchProjectScope(
386 { ...DEFAULT_PR_LIST_CONTROLS, projectId: "scratch", projectEnvironmentId: "env-a" },
387 projects,
388 );
389 expect(controls.projectId).toBeUndefined();
390 expect(controls.projectEnvironmentId).toBeUndefined();
391 });
392
393 it("keeps a repository scope, and one whose projects have not arrived yet", () => {
394 const repository = {
395 ...DEFAULT_PR_LIST_CONTROLS,
396 projectId: "repo",
397 projectEnvironmentId: "env-a",
398 };
399 expect(withoutScratchProjectScope(repository, projects)).toBe(repository);
400 const pending = { ...repository, projectId: "scratch" };
401 expect(withoutScratchProjectScope(pending, [])).toBe(pending);
402 });
403
404 it("keeps another server's project that shares the Scratch folder's id", () => {
405 const elsewhere = {
406 ...DEFAULT_PR_LIST_CONTROLS,
407 projectId: "scratch",
408 projectEnvironmentId: "env-b",
409 };
410 expect(withoutScratchProjectScope(elsewhere, projects)).toBe(elsewhere);
411 });
412});
apps/mobile/src/features/pull-requests/pullRequestList.ts+15
......@@ -15,6 +15,21 @@ export const DEFAULT_PR_LIST_CONTROLS: PullRequestListControls = {
1515 sort: "updated",
1616};
1717
18/** A scope saved on the Scratch folder is no longer offered, so it reads as "All projects". */
19export function withoutScratchProjectScope(
20 controls: PullRequestListControls,
21 projects: readonly { id: string; environmentId: string; isScratch?: true }[],
22): PullRequestListControls {
23 return projects.some(
24 (project) =>
25 project.isScratch &&
26 project.id === controls.projectId &&
27 project.environmentId === controls.projectEnvironmentId,
28 )
29 ? { ...controls, projectId: undefined, projectEnvironmentId: undefined }
30 : controls;
31}
32
1833export function pullRequestListKey(
1934 item: Pick<PullRequestListEntry, "host" | "repository" | "number">,
2035) {
apps/web/src/routes/_chat.pull-requests.tsx+5-2
......@@ -411,9 +411,12 @@ function PullRequestsRouteView() {
411411 // the one wrong answer the empty state can give.
412412 const projectsKnown = useAllEnvironmentShellsBootstrapped();
413413 // Only the projects the page can actually read: one on an environment that cannot list pull
414 // requests could neither be listed nor acted on.
414 // requests could neither be listed nor acted on, and the Scratch folder is never a repository.
415415 const projects = useMemo(
416 () => allProjects.filter((project) => environmentIds.includes(project.environmentId)),
416 () =>
417 allProjects.filter(
418 (project) => !project.isScratch && environmentIds.includes(project.environmentId),
419 ),
417420 [allProjects, environmentIds],
418421 );
419422 const environmentLabels = useMemo(