Speed up issue-to-issue navigation

This commit is contained in:
Dotta 2026-04-11 11:05:32 -05:00
parent 11de5ae9c9
commit 1729e41179
8 changed files with 347 additions and 32 deletions

View file

@ -30,6 +30,7 @@ import {
rememberIssueDetailLocationState,
withIssueDetailHeaderSeed,
} from "../lib/issueDetailBreadcrumb";
import { prefetchIssueDetail } from "../lib/issueDetailCache";
import {
hasBlockingShortcutDialog,
isKeyboardShortcutTextInputTarget,
@ -1578,6 +1579,7 @@ export function Inbox() {
const pathId = issue.identifier ?? issue.id;
const detailState = armIssueDetailInboxQuickArchive(withIssueDetailHeaderSeed(issueLinkState, issue));
rememberIssueDetailLocationState(pathId, detailState);
void prefetchIssueDetail(queryClient, pathId, { issue });
act.navigate(createIssueDetailPath(pathId), { state: detailState });
} else if (item) {
if (item.kind === "issue") {
@ -1586,6 +1588,7 @@ export function Inbox() {
withIssueDetailHeaderSeed(issueLinkState, item.issue),
);
rememberIssueDetailLocationState(pathId, detailState);
void prefetchIssueDetail(queryClient, pathId, { issue: item.issue });
act.navigate(createIssueDetailPath(pathId), { state: detailState });
} else if (item.kind === "approval") {
act.navigate(`/approvals/${item.approval.id}`);

View file

@ -27,6 +27,7 @@ import {
rememberIssueDetailLocationState,
} from "../lib/issueDetailBreadcrumb";
import { resolveIssueActiveRun, shouldTrackIssueActiveRun } from "../lib/issueActiveRun";
import { fetchIssueDetail, getCachedIssueDetail } from "../lib/issueDetailCache";
import {
hasBlockingShortcutDialog,
resolveIssueDetailGoKeyAction,
@ -392,6 +393,24 @@ export function IssueDetail() {
const fileInputRef = useRef<HTMLInputElement | null>(null);
const lastMarkedReadIssueIdRef = useRef<string | null>(null);
const commentComposerRef = useRef<IssueChatComposerHandle | null>(null);
const resolvedIssueDetailState = useMemo(
() => readIssueDetailLocationState(issueId, location.state, location.search),
[issueId, location.state, location.search],
);
const issueHeaderSeed = useMemo(
() => readIssueDetailHeaderSeed(location.state) ?? readIssueDetailHeaderSeed(resolvedIssueDetailState),
[location.state, resolvedIssueDetailState],
);
const cachedIssue = useMemo(
() =>
issueId
? getCachedIssueDetail(queryClient, issueId, issueHeaderSeed ? {
id: issueHeaderSeed.id,
identifier: issueHeaderSeed.identifier,
} : null)
: undefined,
[issueHeaderSeed, issueId, queryClient],
);
useEffect(() => {
setIssueChatInitialTranscriptReady(false);
@ -399,8 +418,9 @@ export function IssueDetail() {
const { data: issue, isLoading, error } = useQuery({
queryKey: queryKeys.issues.detail(issueId!),
queryFn: () => issuesApi.get(issueId!),
queryFn: () => fetchIssueDetail(queryClient, issueId!),
enabled: !!issueId,
initialData: () => cachedIssue,
});
const resolvedCompanyId = issue?.companyId ?? selectedCompanyId;
const commentComposerDisabledReason = useMemo(() => {
@ -491,14 +511,6 @@ export function IssueDetail() {
),
[activeRun, liveRuns],
);
const resolvedIssueDetailState = useMemo(
() => readIssueDetailLocationState(issueId, location.state, location.search),
[issueId, location.state, location.search],
);
const issueHeaderSeed = useMemo(
() => readIssueDetailHeaderSeed(location.state) ?? readIssueDetailHeaderSeed(resolvedIssueDetailState),
[location.state, resolvedIssueDetailState],
);
const sourceBreadcrumb = useMemo(
() => readIssueDetailBreadcrumb(issueId, location.state, location.search) ?? { label: "Issues", href: "/issues" },
[issueId, location.state, location.search],
@ -1639,6 +1651,7 @@ export function IssueDetail() {
<Link
to={createIssueDetailPath(ancestor.identifier ?? ancestor.id)}
state={resolvedIssueDetailState ?? location.state}
issuePrefetch={ancestor}
onClickCapture={() =>
rememberIssueDetailLocationState(
ancestor.identifier ?? ancestor.id,
@ -1883,6 +1896,7 @@ export function IssueDetail() {
key={child.id}
to={createIssueDetailPath(child.identifier ?? child.id)}
state={resolvedIssueDetailState ?? location.state}
issuePrefetch={child}
onClickCapture={() =>
rememberIssueDetailLocationState(
child.identifier ?? child.id,