From 89a74c6b326eb4562f567205bc706fe03a153d7c Mon Sep 17 00:00:00 2001 From: Jinwoo-H Date: Sat, 13 Jun 2026 00:05:21 -0700 Subject: [PATCH] Route PR page metadata by repo host Co-authored-by: Orca --- .../host-context-implementation-checklist.md | 1 + .../src/components/PullRequestPage.tsx | 24 ++++++++--- .../pull-request-page-host-boundary.test.ts | 43 +++++++++++++++++++ 3 files changed, 61 insertions(+), 7 deletions(-) create mode 100644 src/renderer/src/components/pull-request-page-host-boundary.test.ts diff --git a/notes/host-context-implementation-checklist.md b/notes/host-context-implementation-checklist.md index 38cbade9fd8..dfe31feba0c 100644 --- a/notes/host-context-implementation-checklist.md +++ b/notes/host-context-implementation-checklist.md @@ -270,3 +270,4 @@ Done means: reviewers and future agents can understand the vision, implementatio - [x] 2026-06-12: Aligned Automations manual-run disabled states with the backend run-target resolver so saved run contexts now disable Run Now when the project-host setup is missing or not ready, instead of failing after dispatch. Verified with focused automation availability tests, targeted oxlint, full `pnpm run typecheck`, and `git diff --check`. - [x] 2026-06-12: Routed GitHub drawer label/assignee/reviewer metadata reads through the same task source context as issue/PR mutations, including slug-addressed Project view rows and reviewer request/remove actions, so same-repo multi-host drawers do not fall back to the repo owner or focused runtime. Verified with a focused source-boundary test, targeted oxlint, full `pnpm run typecheck`, and `git diff --check`. - [x] 2026-06-13: Tightened runtime project-host setup mutations so older remote servers that advertise `project-host-setup.v1` but not `workspace-run-context.v1` are blocked before setup/clone/create/update/delete RPCs mutate server state; read-only hydration still degrades to repo-derived compatibility rows. Verified with focused repo capability tests, targeted oxlint, full `pnpm run typecheck`, and `git diff --check`. +- [x] 2026-06-13: Routed the full-page Pull Request metadata popovers through the PR repo owner host instead of ambient focused runtime state, so reviewer, label, and assignee reads align with the existing repo-owned mutation routing. Verified with a focused PR host-boundary test, targeted oxlint, full `pnpm run typecheck`, and `git diff --check`. diff --git a/src/renderer/src/components/PullRequestPage.tsx b/src/renderer/src/components/PullRequestPage.tsx index 15cedfc816d..6d0e75e378d 100644 --- a/src/renderer/src/components/PullRequestPage.tsx +++ b/src/renderer/src/components/PullRequestPage.tsx @@ -523,7 +523,6 @@ function PRReviewersPanel({ reviewRequests: item.reviewRequests })) const patchWorkItem = useAppStore((s) => s.patchWorkItem) - const settings = useAppStore((s) => s.settings) const repoOwnerSettings = useAppStore( useShallow((s) => getSettingsForRepoRuntimeOwner(s, item.repoId ?? null)) ) @@ -601,11 +600,12 @@ function PRReviewersPanel({ open && reviewSlug ? reviewSlug.owner : null, open && reviewSlug ? reviewSlug.repo : null, reviewerSeedUsers.map((user) => user.login), - settings + repoOwnerSettings ) const reviewerMetadataByPath = useRepoAssignees( open && !reviewSlug ? repoPath : null, - open && !reviewSlug ? item.repoId : null + open && !reviewSlug ? item.repoId : null, + repoOwnerSettings ) const reviewerMetadata = reviewSlug ? reviewerMetadataBySlug : reviewerMetadataByPath const displayItem = { ...item, reviewRequests: localReviewRequests } @@ -4960,6 +4960,9 @@ function GHEditSection({ const assigneesItemKey = `${item.repoId}\0${item.id}` const patchWorkItem = useAppStore((s) => s.patchWorkItem) const patchProjectRowContent = useAppStore((s) => s.patchProjectRowContent) + const repoOwnerSettings = useAppStore( + useShallow((s) => getSettingsForRepoRuntimeOwner(s, item.repoId ?? repoId ?? null)) + ) const { isPending, run } = useImmediateMutation() // Why: when the dialog opens from a Project view, mutations route through // *BySlug IPCs and we must keep `projectViewCache` in sync alongside @@ -4983,15 +4986,22 @@ function GHEditSection({ const slugRepo = projectOrigin?.repo ?? null const repoLabelsByPath = useRepoLabels( projectOrigin ? null : repoPath, - projectOrigin ? null : repoId + projectOrigin ? null : repoId, + repoOwnerSettings ) - const repoLabelsBySlug = useRepoLabelsBySlug(slugOwner, slugRepo) + const repoLabelsBySlug = useRepoLabelsBySlug(slugOwner, slugRepo, repoOwnerSettings) const repoLabels = projectOrigin ? repoLabelsBySlug : repoLabelsByPath const repoAssigneesByPath = useRepoAssignees( projectOrigin ? null : repoPath, - projectOrigin ? null : repoId + projectOrigin ? null : repoId, + repoOwnerSettings + ) + const repoAssigneesBySlug = useRepoAssigneesBySlug( + slugOwner, + slugRepo, + assignees, + repoOwnerSettings ) - const repoAssigneesBySlug = useRepoAssigneesBySlug(slugOwner, slugRepo, assignees) const repoAssignees = projectOrigin ? repoAssigneesBySlug : repoAssigneesByPath // Why: sync local assignees when item changes or when the detail fetch diff --git a/src/renderer/src/components/pull-request-page-host-boundary.test.ts b/src/renderer/src/components/pull-request-page-host-boundary.test.ts new file mode 100644 index 00000000000..b0321dadf16 --- /dev/null +++ b/src/renderer/src/components/pull-request-page-host-boundary.test.ts @@ -0,0 +1,43 @@ +import { readFileSync } from 'node:fs' +import { join } from 'node:path' +import { describe, expect, it } from 'vitest' + +const COMPONENT_ROOT = __dirname + +function componentSource(relativePath: string): string { + return readFileSync(join(COMPONENT_ROOT, relativePath), 'utf8') +} + +function sourceBetween(source: string, startPattern: string, endPattern: string): string { + const start = source.indexOf(startPattern) + expect(start).toBeGreaterThanOrEqual(0) + const end = source.indexOf(endPattern, start + startPattern.length) + expect(end).toBeGreaterThan(start) + return source.slice(start, end) +} + +describe('PullRequestPage host boundaries', () => { + it('routes reviewer metadata and mutations through the PR repo owner host', () => { + const source = componentSource('PullRequestPage.tsx') + const section = sourceBetween(source, 'function PRReviewersPanel', 'function isPRFileViewed') + + expect(section).toContain('getSettingsForRepoRuntimeOwner(s, item.repoId ?? null)') + expect(section).toContain('useRepoAssigneesBySlug(') + expect(section).toContain('repoOwnerSettings') + expect(section).toContain('useRepoAssignees(') + expect(section).toContain('repoOwnerSettings') + expect(section).toContain('getActiveRuntimeTarget(repoOwnerSettings)') + }) + + it('routes PR edit metadata through the same repo owner host as mutations', () => { + const source = componentSource('PullRequestPage.tsx') + const section = sourceBetween(source, 'function GHEditSection', 'function GHCommentComposer') + + expect(section).toContain('getSettingsForRepoRuntimeOwner(s, item.repoId ?? repoId ?? null)') + expect(section).toContain('useRepoLabels(') + expect(section).toContain('useRepoLabelsBySlug(slugOwner, slugRepo, repoOwnerSettings)') + expect(section).toContain('useRepoAssignees(') + expect(section).toContain('useRepoAssigneesBySlug(') + expect(section).toContain('repoOwnerSettings') + }) +})