Route PR page metadata by repo host

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Jinwoo-H
2026-06-13 00:05:21 -07:00
co-authored by Orca
parent d7d676116f
commit 89a74c6b32
3 changed files with 61 additions and 7 deletions
@@ -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`.
@@ -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
@@ -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')
})
})