fix(web): use Route.useParams() for Link params, ref for GraphQL

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

the normalized ref from context is null for the default repo, but
Link params need the raw URL slug (e.g. "_"). Use Route.useParams()
for Link params and context.ref for GraphQL variables.

also remove unnecessary non-null assertions on params from
Route.useParams() which always returns strings

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

Change summary

webui2/src/routes/$repo/_issues/issues/$id.tsx   | 15 ++++++++-------
webui2/src/routes/$repo/_issues/issues/index.tsx | 10 +++++-----
webui2/src/routes/$repo/_issues/issues/new.tsx   |  8 ++++----
webui2/src/routes/$repo/_issues/user/$id.tsx     | 14 +++++++-------
webui2/src/routes/$repo/commit/$hash.tsx         |  7 ++++---
webui2/src/routes/$repo/index.tsx                | 17 +++++++++--------
6 files changed, 37 insertions(+), 34 deletions(-)

Detailed changes

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

@@ -27,7 +27,8 @@ export const Route = createFileRoute("/$repo/_issues/issues/$id")({
 // Issue detail page (/:repo/issues/:id). Shows title, status, timeline of
 // comments and events, and a sidebar with labels and participants.
 function RouteComponent() {
-  const { ref: repo } = Route.useRouteContext();
+  const { ref } = Route.useRouteContext();
+  const { repo } = Route.useParams();
   const { bugDetailRef } = Route.useLoaderData();
   const { labelsRef } = Route.useRouteContext();
   const { data } = useReadQuery(bugDetailRef);
@@ -43,7 +44,7 @@ function RouteComponent() {
     <div>
       <Link
         to="/$repo/issues"
-        params={{ repo: repo! }}
+        params={{ repo: repo }}
         search={{ q: "status:open", after: "" }}
         className="text-muted-foreground hover:text-foreground mb-4 flex items-center gap-1.5 text-sm"
       >
@@ -53,7 +54,7 @@ function RouteComponent() {
 
       {/* Title row — hover reveals edit button when logged in */}
       <div className="mb-3">
-        <TitleEditor bugPrefix={bug.humanId} title={bug.title} humanId={bug.humanId} ref_={repo} />
+        <TitleEditor bugPrefix={bug.humanId} title={bug.title} humanId={bug.humanId} ref_={ref} />
       </div>
 
       <div className="text-muted-foreground mb-6 flex flex-wrap items-center gap-3 text-sm">
@@ -61,7 +62,7 @@ function RouteComponent() {
         <span>
           <Link
             to="/$repo/user/$id"
-            params={{ repo: repo!, id: bug.author.humanId }}
+            params={{ repo: repo, id: bug.author.humanId }}
             className="text-foreground font-medium hover:underline"
           >
             {bug.author.displayName}
@@ -76,7 +77,7 @@ function RouteComponent() {
         {/* Timeline + comment box */}
         <div className="min-w-0 flex-1 space-y-4">
           <Timeline repo={repo} bugPrefix={bug.humanId} items={bug.timeline.nodes} />
-          <CommentBox bugPrefix={bug.humanId} bugStatus={bug.status} ref_={repo} />
+          <CommentBox bugPrefix={bug.humanId} bugStatus={bug.status} ref_={ref} />
         </div>
 
         {/* Sidebar */}
@@ -84,7 +85,7 @@ function RouteComponent() {
           <LabelEditor
             bugPrefix={bug.humanId}
             currentLabels={bug.labels}
-            ref_={repo}
+            ref_={ref}
             validLabels={validLabels}
           />
 
@@ -100,7 +101,7 @@ function RouteComponent() {
                   <Link
                     key={p.id}
                     to="/$repo/user/$id"
-                    params={{ repo: repo!, id: p.humanId }}
+                    params={{ repo: repo, id: p.humanId }}
                     title={p.displayName}
                   >
                     <Avatar className="size-6">

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

@@ -46,7 +46,7 @@ const PAGE_SIZE = 25;
 type StatusFilter = "open" | "closed";
 
 function RouteComponent() {
-  const { ref: repo } = Route.useRouteContext();
+  const { repo } = Route.useParams();
   const navigate = useNavigate({ from: "/$repo/issues/" });
   const { q, after } = Route.useSearch();
 
@@ -130,7 +130,7 @@ function RouteComponent() {
           <div className="flex shrink-0 items-center gap-1">
             <Link
               to="/$repo/issues"
-              params={{ repo: repo! }}
+              params={{ repo: repo }}
               search={{ q: queryWithStatus("open"), after: "" }}
               className={cn(
                 "flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium transition-colors",
@@ -153,7 +153,7 @@ function RouteComponent() {
 
             <Link
               to="/$repo/issues"
-              params={{ repo: repo! }}
+              params={{ repo: repo }}
               search={{ q: queryWithStatus("closed"), after: "" }}
               className={cn(
                 "flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium transition-colors",
@@ -232,7 +232,7 @@ function RouteComponent() {
           <div className="border-border flex items-center justify-center gap-2 border-t px-4 py-2">
             <ButtonLink
               to="/$repo/issues"
-              params={{ repo: repo! }}
+              params={{ repo: repo }}
               search={{ q, after: "" }}
               variant="ghost"
               size="sm"
@@ -247,7 +247,7 @@ function RouteComponent() {
             </span>
             <ButtonLink
               to="/$repo/issues"
-              params={{ repo: repo! }}
+              params={{ repo: repo }}
               search={{ q, after: bugs?.pageInfo.endCursor ?? "" }}
               variant="ghost"
               size="sm"

webui2/src/routes/$repo/_issues/issues/new.tsx 🔗

@@ -16,7 +16,7 @@ export const Route = createFileRoute("/$repo/_issues/issues/new")({
 // New issue form (/:repo/issues/new). Title + body with write/preview tabs.
 function RouteComponent() {
   const navigate = useNavigate();
-  const { ref: repo } = Route.useRouteContext();
+  const { repo } = Route.useParams();
   const [title, setTitle] = useState("");
   const [message, setMessage] = useState("");
   const [preview, setPreview] = useState(false);
@@ -31,7 +31,7 @@ function RouteComponent() {
     if (humanId) {
       void navigate({
         to: "/$repo/issues/$id",
-        params: { repo: repo!, id: humanId },
+        params: { repo: repo, id: humanId },
       });
     }
   }
@@ -40,7 +40,7 @@ function RouteComponent() {
     <div className="mx-auto max-w-3xl">
       <Link
         to="/$repo/issues"
-        params={{ repo: repo! }}
+        params={{ repo: repo }}
         search={{ q: "status:open", after: "" }}
         className="text-muted-foreground hover:text-foreground mb-6 flex items-center gap-1.5 text-sm"
       >
@@ -111,7 +111,7 @@ function RouteComponent() {
         <div className="flex justify-end gap-2">
           <ButtonLink
             to="/$repo/issues"
-            params={{ repo: repo! }}
+            params={{ repo: repo }}
             search={{ q: "status:open", after: "" }}
             variant="ghost"
           >

webui2/src/routes/$repo/_issues/user/$id.tsx 🔗

@@ -6,7 +6,7 @@
 // The :id param is treated as a humanId prefix and passed directly to the
 // identity(prefix) and allBugs(query:"author:...") GraphQL arguments.
 
-import { createFileRoute, useParams, Link } from "@tanstack/react-router";
+import { createFileRoute, Link } from "@tanstack/react-router";
 import { formatDistanceToNow } from "date-fns";
 import {
   ArrowLeft,
@@ -33,8 +33,8 @@ export const Route = createFileRoute("/$repo/_issues/user/$id")({
 const PAGE_SIZE = 25;
 
 function RouteComponent() {
-  const { id } = useParams({ strict: false });
-  const { ref: repo } = Route.useRouteContext();
+  const { id, repo } = Route.useParams();
+  const { ref } = Route.useRouteContext();
   const [statusFilter, setStatusFilter] = useState<"open" | "closed">("open");
 
   // Cursor-stack pagination: cursors[i] is the `after` value to fetch page i.
@@ -47,8 +47,8 @@ function RouteComponent() {
   //   bugs — paginated list for the selected tab
   const { data, loading, error } = useUserProfileQuery({
     variables: {
-      ref: repo,
-      prefix: id!,
+      ref,
+      prefix: id,
       openQuery: `author:${id} status:open`,
       closedQuery: `author:${id} status:closed`,
       listQuery: `author:${id} status:${statusFilter}`,
@@ -98,7 +98,7 @@ function RouteComponent() {
     <div>
       <Link
         to="/$repo/issues"
-        params={{ repo: repo! }}
+        params={{ repo: repo }}
         search={{ q: "status:open", after: "" }}
         className="text-muted-foreground hover:text-foreground mb-6 flex items-center gap-1.5 text-sm"
       >
@@ -218,7 +218,7 @@ function RouteComponent() {
                 <div className="flex flex-wrap items-baseline gap-2">
                   <Link
                     to="/$repo/issues/$id"
-                    params={{ repo: repo!, id: bug.humanId }}
+                    params={{ repo: repo, id: bug.humanId }}
                     className="text-foreground hover:text-primary font-medium hover:underline"
                   >
                     {bug.title}

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

@@ -64,7 +64,8 @@ export const Route = createFileRoute("/$repo/commit/$hash")({
 });
 
 function RouteComponent() {
-  const { ref: repo } = Route.useRouteContext();
+  const { ref } = Route.useRouteContext();
+  const { repo } = Route.useParams();
   const { commitRef } = Route.useLoaderData();
   const { data } = useReadQuery(commitRef);
 
@@ -115,7 +116,7 @@ function RouteComponent() {
               parent{" "}
               <Link
                 to="/$repo/commit/$hash"
-                params={{ repo: repo!, hash: p }}
+                params={{ repo, hash: p }}
                 className="text-foreground font-mono hover:underline"
               >
                 {p.slice(0, 7)}
@@ -136,7 +137,7 @@ function RouteComponent() {
           {files.map((file: { path: string; oldPath?: string | null; status: string }) => (
             <FileDiffView
               key={file.path}
-              repo={repo}
+              repo={ref}
               hash={commit.hash}
               path={file.path}
               oldPath={file.oldPath ?? undefined}

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

@@ -133,7 +133,8 @@ export const Route = createFileRoute("/$repo/")({
 });
 
 function RouteComponent() {
-  const { ref: repo } = Route.useRouteContext();
+  const { ref: repoRef } = Route.useRouteContext();
+  const { repo } = Route.useParams();
   const navigate = useNavigate({ from: "/$repo/" });
   const { ref: currentRef, path: currentPath, type: viewMode } = useSearch({ from: "/$repo/" });
 
@@ -157,14 +158,14 @@ function RouteComponent() {
   const inBlobMode = viewMode === "blob" && !!currentRef && !!currentPath;
 
   const { data: treeData, loading: treeLoading } = useQuery<TreeQueryData>(TREE_QUERY, {
-    variables: { repo, ref: currentRef, path: currentPath || null },
+    variables: { repo: repoRef, ref: currentRef, path: currentPath || null },
     skip: !inTreeMode,
   });
   const entries: GitTreeEntry[] = treeData?.repository?.tree ?? [];
 
   const entryNames = entries.map((e: GitTreeEntry) => e.name);
   const { data: lastCommitsData } = useQuery<LastCommitsQueryData>(LAST_COMMITS_QUERY, {
-    variables: { repo, ref: currentRef, path: currentPath || null, names: entryNames },
+    variables: { repo: repoRef, ref: currentRef, path: currentPath || null, names: entryNames },
     skip: !inTreeMode || entryNames.length === 0,
   });
   const lastCommitsByName = new Map<string, GitLastCommit>(
@@ -176,7 +177,7 @@ function RouteComponent() {
   }));
 
   const { data: blobData, loading: blobLoading } = useQuery<BlobQueryData>(BLOB_QUERY, {
-    variables: { repo, ref: currentRef, path: currentPath },
+    variables: { repo: repoRef, ref: currentRef, path: currentPath },
     skip: !inBlobMode,
   });
   const blob: GitBlob | null = blobData?.repository?.blob ?? null;
@@ -191,12 +192,12 @@ function RouteComponent() {
       : readmeEntry.name
     : null;
   const { data: readmeBlobData } = useQuery<BlobQueryData>(BLOB_QUERY, {
-    variables: { repo, ref: currentRef, path: readmePath },
+    variables: { repo: repoRef, ref: currentRef, path: readmePath },
     skip: !inTreeMode || !readmePath,
   });
   const readme: string | null = readmeBlobData?.repository?.blob?.text ?? null;
 
-  const repoName = refsData?.repository?.name ?? repo ?? "default-repo";
+  const repoName = refsData?.repository?.name ?? repoRef ?? "default-repo";
 
   function navigateTo(path: string, type: ViewMode = "tree") {
     void navigate({ search: (prev) => ({ ...prev, path, type }) });
@@ -239,7 +240,7 @@ function RouteComponent() {
         <div className="flex items-center gap-2">
           <ButtonLink
             to="/$repo"
-            params={{ repo: repo! }}
+            params={{ repo }}
             search={{
               ref: currentRef,
               path: currentPath,
@@ -256,7 +257,7 @@ function RouteComponent() {
       </div>
 
       {viewMode === "commits" ? (
-        <CommitList repo={repo} ref_={currentRef} path={currentPath || undefined} />
+        <CommitList repo={repoRef} ref_={currentRef} path={currentPath || undefined} />
       ) : viewMode === "tree" || !blob ? (
         <>
           <FileTree