From 0ab4e4f2b9c8a640d12d764539032106a2d8519f Mon Sep 17 00:00:00 2001 From: Jinwoo-H Date: Thu, 6 Aug 2026 22:02:33 -0400 Subject: [PATCH] fix(mobile): load the repo list in GitHub Project mode loadTasks returned before loadRepos() when the GitHub mode was `project`, so `repos` stayed empty, `hostedRepos` was empty, and filterGitHubProjectRowsForRepos dropped every board row. Visiting any other mode populated `repos` as a side effect, which is why tapping Issues once and coming back appeared to fix it. The active mode syncs from the desktop, so a fresh launch can land straight in Projects and show "No project items" for a board that has items. Move the project-mode short-circuit below the repo list load. Project mode still fetches no issues/PRs; it just no longer skips the repo list it needs to match rows against. Fixes #12966 --- mobile/app/h/[hostId]/tasks.tsx | 10 ++-- .../github-project-repo-list-load.test.ts | 52 +++++++++++++++++++ 2 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 mobile/src/tasks/github-project-repo-list-load.test.ts diff --git a/mobile/app/h/[hostId]/tasks.tsx b/mobile/app/h/[hostId]/tasks.tsx index 0794c317e41..a47b616eda7 100644 --- a/mobile/app/h/[hostId]/tasks.tsx +++ b/mobile/app/h/[hostId]/tasks.tsx @@ -3257,10 +3257,6 @@ export default function MobileTasksScreen() { setGithubSourceErrors([]) setGithubSourceFallbacks([]) } - if (provider === 'github' && githubMode === 'project') { - setItems([]) - return - } if (provider === 'linear' && !linearConnected) { setItems([]) return @@ -3269,6 +3265,12 @@ export default function MobileTasksScreen() { if (!isCurrent()) { return } + // Why: project mode fetches no work items, but its rows are matched against + // the repo list, so it must not return before loadRepos() has run. + if (provider === 'github' && githubMode === 'project') { + setItems([]) + return + } if (provider === 'github' || provider === 'gitlab') { const supportedRepos = currentRepos.filter(isHostedTaskRepo) const queriedRepos = diff --git a/mobile/src/tasks/github-project-repo-list-load.test.ts b/mobile/src/tasks/github-project-repo-list-load.test.ts new file mode 100644 index 00000000000..8065dfd7c95 --- /dev/null +++ b/mobile/src/tasks/github-project-repo-list-load.test.ts @@ -0,0 +1,52 @@ +import { readFileSync } from 'node:fs' +import { describe, expect, it } from 'vitest' + +const source = readFileSync(new URL('../../app/h/[hostId]/tasks.tsx', import.meta.url), 'utf8') + +// loadTasks is the only path that calls loadRepos, so the ordering inside it is +// what decides whether Project rows have repos to match against. +function loadTasksBody(): string { + const start = source.indexOf('const loadTasks = useCallback(') + expect(start, 'loadTasks must exist in the tasks route').toBeGreaterThan(-1) + const end = source.indexOf('const connectLinearAccount = useCallback(', start) + expect(end, 'loadTasks must be followed by connectLinearAccount').toBeGreaterThan(start) + return source.slice(start, end) +} + +function offsets(): { + repoLoad: number + projectReturn: number + workItemFetch: number +} { + const body = loadTasksBody() + return { + repoLoad: body.indexOf('await loadRepos()'), + projectReturn: body.indexOf("provider === 'github' && githubMode === 'project'"), + workItemFetch: body.indexOf("provider === 'github' || provider === 'gitlab'") + } +} + +describe('mobile GitHub Project repo list loading', () => { + it('keeps loadRepos on the loadTasks path', () => { + expect(offsets().repoLoad, 'loadTasks must still load the repo list').toBeGreaterThan(-1) + }) + + // Regression: project mode returned before loadRepos(), so hostedRepos stayed + // empty, filterGitHubProjectRowsForRepos matched nothing, and the board read + // "No project items" until another mode populated repos as a side effect. + it('loads the repo list before project mode bails out', () => { + const { repoLoad, projectReturn } = offsets() + expect(projectReturn, 'project mode must still short-circuit loadTasks').toBeGreaterThan(-1) + expect(projectReturn, 'project mode must bail out after the repo list load').toBeGreaterThan( + repoLoad + ) + }) + + it('still bails out before any work-item fetch in project mode', () => { + const { projectReturn, workItemFetch } = offsets() + expect(workItemFetch, 'the hosted work-item fetch must still exist').toBeGreaterThan(-1) + expect(workItemFetch, 'project mode must not reach the issue/PR fetch').toBeGreaterThan( + projectReturn + ) + }) +})