Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions src/workstation/chrome/previewPane.triage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,32 @@ describe('hydrated triage preview sections (inspector hydration follow-up)', ()
expect(lines.some((l) => l.toLowerCase().includes('loading'))).toBe(true)
})

it('issue preview shows the failure message instead of the loading hint when hydration failed', () => {
const lines = formatIssueTriagePreview(issue, undefined, 'HTTP 401: Bad credentials').map(stripLine)
expect(lines.some((l) => l.includes('Failed to load details: HTTP 401: Bad credentials'))).toBe(true)
expect(lines.some((l) => l.toLowerCase().includes('loading'))).toBe(false)
})

it('issue preview ignores a cached error once detail is present', () => {
const detail = { number: 882, body: 'Body text.', comments: [] }
const lines = formatIssueTriagePreview(issue, detail, 'stale error').map(stripLine)
expect(lines.some((l) => l.includes('stale error'))).toBe(false)
expect(lines).toContain('Body text.')
})

it('PR preview shows the failure message instead of the loading hint when hydration failed', () => {
const lines = formatPullRequestTriagePreview(pr, undefined, 'pull request', 'rate limited').map(stripLine)
expect(lines.some((l) => l.includes('Failed to load details: rate limited'))).toBe(true)
expect(lines.some((l) => l.toLowerCase().includes('loading'))).toBe(false)
})

it('PR preview ignores a cached error once detail is present', () => {
const detail = { number: 962, body: 'Body text.', comments: [], reviews: [], statusCheckRollup: [] }
const lines = formatPullRequestTriagePreview(pr, detail, 'pull request', 'stale error').map(stripLine)
expect(lines.some((l) => l.includes('stale error'))).toBe(false)
expect(lines).toContain('Body text.')
})

it('PR preview renders body + checks + reviews + comments when detail is provided', () => {
const detail = {
number: 962,
Expand Down
16 changes: 14 additions & 2 deletions src/workstation/chrome/previewPane.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,8 @@ function statusChecksSection(
*/
export function formatIssueTriagePreview(
issue: IssueListItem | undefined,
detail?: IssueDetail
detail?: IssueDetail,
error?: string
): PreviewLine[] {
if (!issue) {
return [dim('Select an issue to preview.')]
Expand Down Expand Up @@ -292,6 +293,11 @@ export function formatIssueTriagePreview(
out.push(blank())
out.push(...comments)
}
} else if (error) {
// #OSS-1770 — the debounced hydration effect failed; show the
// forge's message instead of a permanent "Loading…" placeholder.
out.push(blank())
out.push(dim(`Failed to load details: ${error}`))
} else if (typeof issue.comments === 'number' && issue.comments > 0) {
// Pre-hydration affordance — tell the user the body / comments
// section is coming, so a 250ms wait doesn't look like a bug.
Expand All @@ -313,7 +319,8 @@ export function formatIssueTriagePreview(
export function formatPullRequestTriagePreview(
pr: PullRequestListItem | undefined,
detail?: PullRequestDetail,
nounLower = 'pull request'
nounLower = 'pull request',
error?: string
): PreviewLine[] {
if (!pr) {
return [dim(`Select a ${nounLower} to preview.`)]
Expand Down Expand Up @@ -370,6 +377,11 @@ export function formatPullRequestTriagePreview(
out.push(blank())
out.push(...comments)
}
} else if (error) {
// #OSS-1770 — the debounced hydration effect failed; show the
// forge's message instead of a permanent "Loading…" placeholder.
out.push(blank())
out.push(dim(`Failed to load details: ${error}`))
} else {
// Pre-hydration affordance — same as the issue preview.
out.push(blank())
Expand Down
41 changes: 37 additions & 4 deletions src/workstation/runtime/hooks/useDetailHydration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,13 +190,29 @@
Math.min(state.selectedIssueIndex, Math.max(0, filteredIssueList.length - 1))
]
if (!cursored) return
if (context.issueDetailByNumber?.has(cursored.number)) return
if (
context.issueDetailByNumber?.has(cursored.number) ||
context.issueDetailErrorByNumber?.has(cursored.number)
) return

const issuedAtDepth = runtimes.length - 1
let active = true
const timer = setTimeout(async () => {
const result = await forge.getIssueDetail(cursored.number)
if (!active || !result.ok) return
if (!active) return
if (!result.ok) {
setContext(
(current) => ({
...current,
issueDetailErrorByNumber: new Map(current.issueDetailErrorByNumber || []).set(
cursored.number,
result.message
),
}),
issuedAtDepth,
)
return
}
setContext(
(current) => ({
...current,
Expand All @@ -219,6 +235,7 @@
state.selectedIssueIndex,
filteredIssueList,
context.issueDetailByNumber,
context.issueDetailErrorByNumber,
setContext,
])

Expand All @@ -236,13 +253,28 @@
)
]
if (!cursored) return
if (context.pullRequestDetailByNumber?.has(cursored.number)) return
if (
context.pullRequestDetailByNumber?.has(cursored.number) ||
context.pullRequestDetailErrorByNumber?.has(cursored.number)
) return

const issuedAtDepth = runtimes.length - 1
let active = true
const timer = setTimeout(async () => {
const result = await forge.getPullRequestDetail(cursored.number)
if (!active || !result.ok) return
if (!active) return

Check warning on line 265 in src/workstation/runtime/hooks/useDetailHydration.ts

View workflow job for this annotation

GitHub Actions / Quality & package

React Hook React.useEffect has a missing dependency: 'forge'. Either include it or remove the dependency array

Check warning on line 265 in src/workstation/runtime/hooks/useDetailHydration.ts

View workflow job for this annotation

GitHub Actions / Quality & package

React Hook React.useEffect has a missing dependency: 'forge'. Either include it or remove the dependency array
if (!result.ok) {
setContext(
(current) => ({
...current,
pullRequestDetailErrorByNumber: new Map(
current.pullRequestDetailErrorByNumber || []
).set(cursored.number, result.message),
}),
issuedAtDepth,
)
return
}
setContext(
(current) => ({
...current,
Expand All @@ -268,6 +300,7 @@
state.selectedPullRequestTriageIndex,
filteredPullRequestTriageList,
context.pullRequestDetailByNumber,
context.pullRequestDetailErrorByNumber,
setContext,
])

Expand All @@ -291,7 +324,7 @@
if (!active) return
setContext(
(current) => ({
...current,

Check warning on line 327 in src/workstation/runtime/hooks/useDetailHydration.ts

View workflow job for this annotation

GitHub Actions / Quality & package

React Hook React.useEffect has a missing dependency: 'forge'. Either include it or remove the dependency array
blameByPath: new Map(current.blameByPath || []).set(result.path, result),
}),
issuedAtDepth,
Expand Down Expand Up @@ -349,7 +382,7 @@
state.activeView,
state.fileHistoryPath,
context.fileHistoryByPath,
setContext,

Check warning on line 385 in src/workstation/runtime/hooks/useDetailHydration.ts

View workflow job for this annotation

GitHub Actions / Quality & package

React Hook React.useEffect has missing dependencies: 'setBlameFailure' and 'setBlameLoading'. Either include them or remove the dependency array
])

// Commit-note hydration (#OSS-2057). Same debounce / active-flag /
Expand Down
22 changes: 22 additions & 0 deletions src/workstation/runtime/hooks/useForgeTriageWorkflowActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,17 @@ export function createForgeTriageWorkflowHandlers(
next.issueDetailByNumber = undefined
}
}
// #OSS-1770 — clear any cached failure alongside the detail entry
// so a post-mutation re-hydration isn't blocked by a stale error.
if (current.issueDetailErrorByNumber) {
if (typeof issueNumber === 'number') {
const trimmed = new Map(current.issueDetailErrorByNumber)
trimmed.delete(issueNumber)
next.issueDetailErrorByNumber = trimmed
} else {
next.issueDetailErrorByNumber = undefined
}
}
return next
}, issuedAtDepth)
setContextStatus(
Expand All @@ -109,6 +120,17 @@ export function createForgeTriageWorkflowHandlers(
next.pullRequestDetailByNumber = undefined
}
}
// #OSS-1770 — clear any cached failure alongside the detail entry
// so a post-mutation re-hydration isn't blocked by a stale error.
if (current.pullRequestDetailErrorByNumber) {
if (typeof pullRequestNumber === 'number') {
const trimmed = new Map(current.pullRequestDetailErrorByNumber)
trimmed.delete(pullRequestNumber)
next.pullRequestDetailErrorByNumber = trimmed
} else {
next.pullRequestDetailErrorByNumber = undefined
}
}
return next
}, issuedAtDepth)
setContextStatus(
Expand Down
16 changes: 16 additions & 0 deletions src/workstation/runtime/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,12 +123,28 @@ export type LogInkContext = {
* list snappy.
*/
issueDetailByNumber?: Map<number, IssueDetail>
/**
* Per-issue detail *failure* cache keyed by issue number (OSS-1770).
* Set when `forge.getIssueDetail` resolves `{ ok: false, message }` so
* the triage preview pane can render the forge's message instead of a
* permanent "Loading…" placeholder, and so the hydration effect's
* cache-skip guard stops silently re-issuing the same failing fetch
* every time the row is re-cursored. Cleared by the mutation
* invalidation path so a resolved transient failure (e.g. rate limit)
* retries on the next hydration.
*/
issueDetailErrorByNumber?: Map<number, string>
/**
* Per-PR detail cache keyed by pull-request number (#882
* inspector hydration). Mirrors `issueDetailByNumber` — fetched
* via `gh pr view <#>` and cached per session.
*/
pullRequestDetailByNumber?: Map<number, PullRequestDetail>
/**
* Per-PR detail *failure* cache keyed by pull-request number
* (OSS-1770). Mirrors `issueDetailErrorByNumber`.
*/
pullRequestDetailErrorByNumber?: Map<number, string>
reflog?: ReflogOverview
/**
* Remote overview (#0.71). Carries every configured remote's name +
Expand Down
10 changes: 8 additions & 2 deletions src/workstation/surfaces/detail/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1121,8 +1121,11 @@ export function renderIssueTriagePreviewPanel(
const detail = issue
? context.issueDetailByNumber?.get(issue.number)
: undefined
const detailError = issue
? context.issueDetailErrorByNumber?.get(issue.number)
: undefined
return renderPreviewPanel(h, { Box, Text }, 'Issue preview',
formatIssueTriagePreview(issue, detail), width, theme, focused)
formatIssueTriagePreview(issue, detail, detailError), width, theme, focused)
}

/**
Expand Down Expand Up @@ -1168,6 +1171,9 @@ export function renderPullRequestTriagePreviewPanel(
const detail = pr
? context.pullRequestDetailByNumber?.get(pr.number)
: undefined
const detailError = pr
? context.pullRequestDetailErrorByNumber?.get(pr.number)
: undefined
return renderPreviewPanel(h, { Box, Text }, `${nouns.singular} preview`,
formatPullRequestTriagePreview(pr, detail, nouns.singularLower), width, theme, focused)
formatPullRequestTriagePreview(pr, detail, nouns.singularLower, detailError), width, theme, focused)
}
Loading