fix(web): await preloaded queries before route transition

Quentin Gliech and Claude Opus 4.6 (1M context) created

use preloadQuery.toPromise() in all route loaders so the router
waits for data to be ready before transitioning, instead of
rendering the component with a pending query ref

loaders with multiple queries use Promise.all to await them
in parallel

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Change summary

webui2/src/routes/$repo/commit/$hash.tsx | 10 +++--
webui2/src/routes/$repo/index.tsx        | 10 +++--
webui2/src/routes/$repo/issues/$id.tsx   | 19 ++++++----
webui2/src/routes/$repo/issues/index.tsx | 42 ++++++++++++++-----------
webui2/src/routes/index.tsx              |  8 +++-
5 files changed, 51 insertions(+), 38 deletions(-)

Detailed changes

webui2/src/routes/$repo/commit/$hash.tsx 🔗

@@ -57,11 +57,13 @@ interface CommitQueryData {
 export const Route = createFileRoute("/$repo/commit/$hash")({
   component: RouteComponent,
   pendingComponent: CommitPageSkeleton,
-  loader: ({ params: { repo, hash } }) => ({
-    commitRef: preloadQuery<CommitQueryData>(COMMIT_QUERY, {
+  loader: async ({ params: { repo, hash } }) => {
+    const commitRef = preloadQuery<CommitQueryData>(COMMIT_QUERY, {
       variables: { repo: repo === "_" ? null : repo, hash },
-    }),
-  }),
+    });
+    await preloadQuery.toPromise(commitRef);
+    return { commitRef };
+  },
 });
 
 function RouteComponent() {

webui2/src/routes/$repo/index.tsx 🔗

@@ -126,11 +126,13 @@ export const Route = createFileRoute("/$repo/")({
   component: RouteComponent,
   pendingComponent: CodePageSkeleton,
   validateSearch: (search) => v.parse(codePageSearchSchema, search),
-  loader: ({ params: { repo } }) => ({
-    refsRef: preloadQuery<RefsQueryData>(REFS_QUERY, {
+  loader: async ({ params: { repo } }) => {
+    const refsRef = preloadQuery<RefsQueryData>(REFS_QUERY, {
       variables: { repo: repo === "_" ? null : repo },
-    }),
-  }),
+    });
+    await preloadQuery.toPromise(refsRef);
+    return { refsRef };
+  },
 });
 
 function RouteComponent() {

webui2/src/routes/$repo/issues/$id.tsx 🔗

@@ -23,14 +23,17 @@ import { useRepo } from "@/lib/repo";
 export const Route = createFileRoute("/$repo/issues/$id")({
   component: RouteComponent,
   pendingComponent: BugDetailSkeleton,
-  loader: ({ params: { repo, id } }) => ({
-    bugDetailRef: preloadQuery<BugDetailQuery>(BugDetailDocument, {
-      variables: { ref: repo === "_" ? null : repo, prefix: id },
-    }),
-    labelsRef: preloadQuery<ValidLabelsQuery>(ValidLabelsDocument, {
-      variables: { ref: repo === "_" ? null : repo },
-    }),
-  }),
+  loader: async ({ params: { repo, id } }) => {
+    const ref = repo === "_" ? null : repo;
+    const bugDetailRef = preloadQuery<BugDetailQuery>(BugDetailDocument, {
+      variables: { ref, prefix: id },
+    });
+    const labelsRef = preloadQuery<ValidLabelsQuery>(ValidLabelsDocument, {
+      variables: { ref },
+    });
+    await Promise.all([preloadQuery.toPromise(bugDetailRef), preloadQuery.toPromise(labelsRef)]);
+    return { bugDetailRef, labelsRef };
+  },
 });
 
 // Issue detail page (/:repo/issues/:id). Shows title, status, timeline of

webui2/src/routes/$repo/issues/index.tsx 🔗

@@ -33,28 +33,32 @@ export const Route = createFileRoute("/$repo/issues/")({
   pendingComponent: BugListSkeleton,
   validateSearch: (search) => v.parse(issuesSearchSchema, search),
   loaderDeps: ({ search: { q, after } }) => ({ q, after }),
-  loader: ({ params: { repo }, deps: { q, after } }) => {
+  loader: async ({ params: { repo }, deps: { q, after } }) => {
     const ref = repo === "_" ? null : repo;
     const parsed = parseQueryString(q);
     const baseQuery = buildBaseQuery(parsed.labels, parsed.author, parsed.freeText);
-    return {
-      bugListRef: preloadQuery<BugListQuery>(BugListDocument, {
-        variables: {
-          ref,
-          openQuery: `status:open ${baseQuery}`.trim(),
-          closedQuery: `status:closed ${baseQuery}`.trim(),
-          listQuery: q,
-          first: PAGE_SIZE,
-          after: after || undefined,
-        },
-      }),
-      labelsRef: preloadQuery<ValidLabelsQuery>(ValidLabelsDocument, {
-        variables: { ref },
-      }),
-      identitiesRef: preloadQuery<AllIdentitiesQuery>(AllIdentitiesDocument, {
-        variables: { ref },
-      }),
-    };
+    const bugListRef = preloadQuery<BugListQuery>(BugListDocument, {
+      variables: {
+        ref,
+        openQuery: `status:open ${baseQuery}`.trim(),
+        closedQuery: `status:closed ${baseQuery}`.trim(),
+        listQuery: q,
+        first: PAGE_SIZE,
+        after: after || undefined,
+      },
+    });
+    const labelsRef = preloadQuery<ValidLabelsQuery>(ValidLabelsDocument, {
+      variables: { ref },
+    });
+    const identitiesRef = preloadQuery<AllIdentitiesQuery>(AllIdentitiesDocument, {
+      variables: { ref },
+    });
+    await Promise.all([
+      preloadQuery.toPromise(bugListRef),
+      preloadQuery.toPromise(labelsRef),
+      preloadQuery.toPromise(identitiesRef),
+    ]);
+    return { bugListRef, labelsRef, identitiesRef };
   },
 });
 

webui2/src/routes/index.tsx 🔗

@@ -11,9 +11,11 @@ import { preloadQuery } from "@/lib/apollo";
 
 export const Route = createFileRoute("/")({
   component: RouteComponent,
-  loader: () => ({
-    repositoriesRef: preloadQuery<RepositoriesQuery>(RepositoriesDocument),
-  }),
+  loader: async () => {
+    const repositoriesRef = preloadQuery<RepositoriesQuery>(RepositoriesDocument);
+    await preloadQuery.toPromise(repositoriesRef);
+    return { repositoriesRef };
+  },
 });
 
 function repoSlug(name: string | null | undefined): string {